Skip to content

Commit 776a689

Browse files
Merge pull request nextcloud#54385 from nextcloud/fix/51946/split-discovery-capacities
feat(ocm): split ocm discovery and capacities
2 parents 357292f + fa60488 commit 776a689

File tree

8 files changed

+112
-97
lines changed

8 files changed

+112
-97
lines changed

apps/cloud_federation_api/lib/Capabilities.php

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,14 @@
99

1010
namespace OCA\CloudFederationAPI;
1111

12-
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
13-
use NCU\Security\Signature\Exceptions\SignatoryException;
14-
use OC\OCM\OCMSignatoryManager;
12+
use OC\OCM\OCMDiscoveryService;
1513
use OCP\Capabilities\ICapability;
1614
use OCP\Capabilities\IInitialStateExcludedCapability;
17-
use OCP\IAppConfig;
18-
use OCP\IURLGenerator;
1915
use OCP\OCM\Exceptions\OCMArgumentException;
20-
use OCP\OCM\ICapabilityAwareOCMProvider;
21-
use Psr\Log\LoggerInterface;
2216

2317
class Capabilities implements ICapability, IInitialStateExcludedCapability {
24-
public const API_VERSION = '1.1.0';
25-
2618
public function __construct(
27-
private IURLGenerator $urlGenerator,
28-
private IAppConfig $appConfig,
29-
private ICapabilityAwareOCMProvider $provider,
30-
private readonly OCMSignatoryManager $ocmSignatoryManager,
31-
private readonly LoggerInterface $logger,
19+
private readonly OCMDiscoveryService $ocmDiscoveryService,
3220
) {
3321
}
3422

@@ -39,40 +27,7 @@ public function __construct(
3927
* @throws OCMArgumentException
4028
*/
4129
public function getCapabilities() {
42-
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
43-
$pos = strrpos($url, '/');
44-
if ($pos === false) {
45-
throw new OCMArgumentException('generated route should contain a slash character');
46-
}
47-
48-
$this->provider->setEnabled(true);
49-
$this->provider->setApiVersion(self::API_VERSION);
50-
$this->provider->setCapabilities(['/invite-accepted', '/notifications', '/shares']);
51-
52-
$this->provider->setEndPoint(substr($url, 0, $pos));
53-
54-
$resource = $this->provider->createNewResourceType();
55-
$resource->setName('file')
56-
->setShareTypes(['user', 'group'])
57-
->setProtocols(['webdav' => '/public.php/webdav/']);
58-
59-
$this->provider->addResourceType($resource);
60-
61-
// Adding a public key to the ocm discovery
62-
try {
63-
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
64-
/**
65-
* @experimental 31.0.0
66-
* @psalm-suppress UndefinedInterfaceMethod
67-
*/
68-
$this->provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
69-
} else {
70-
$this->logger->debug('ocm public key feature disabled');
71-
}
72-
} catch (SignatoryException|IdentityNotFoundException $e) {
73-
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
74-
}
75-
76-
return ['ocm' => $this->provider->jsonSerialize()];
30+
$provider = $this->ocmDiscoveryService->getLocalOCMProvider(false);
31+
return ['ocm' => $provider->jsonSerialize()];
7732
}
7833
}

core/AppInfo/ConfigLexicon.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ConfigLexicon implements ILexicon {
2727
public const SHARE_LINK_EXPIRE_DATE_DEFAULT = 'shareapi_default_expire_date';
2828
public const SHARE_LINK_EXPIRE_DATE_ENFORCED = 'shareapi_enforce_expire_date';
2929
public const USER_LANGUAGE = 'lang';
30+
public const OCM_DISCOVERY_ENABLED = 'ocm_discovery_enabled';
3031
public const USER_LOCALE = 'locale';
3132
public const USER_TIMEZONE = 'timezone';
3233

@@ -85,6 +86,7 @@ public function getAppConfigs(): array {
8586
definition: 'Enforce expiration date for shares via link or mail'
8687
),
8788
new Entry(self::LASTCRON_TIMESTAMP, ValueType::INT, 0, 'timestamp of last cron execution'),
89+
new Entry(self::OCM_DISCOVERY_ENABLED, ValueType::BOOL, true, 'enable/disable OCM', lazy: true),
8890
];
8991
}
9092

core/Controller/OCMController.php

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,17 @@
1010
namespace OC\Core\Controller;
1111

1212
use Exception;
13-
use OCA\CloudFederationAPI\Capabilities;
13+
use OC\OCM\OCMDiscoveryService;
1414
use OCP\AppFramework\Controller;
1515
use OCP\AppFramework\Http;
1616
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
1717
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
1818
use OCP\AppFramework\Http\Attribute\OpenAPI;
1919
use OCP\AppFramework\Http\Attribute\PublicPage;
2020
use OCP\AppFramework\Http\DataResponse;
21-
use OCP\Capabilities\ICapability;
2221
use OCP\IAppConfig;
2322
use OCP\IRequest;
2423
use OCP\Server;
25-
use Psr\Container\ContainerExceptionInterface;
2624
use Psr\Log\LoggerInterface;
2725

2826
/**
@@ -34,6 +32,7 @@ class OCMController extends Controller {
3432
public function __construct(
3533
IRequest $request,
3634
private readonly IAppConfig $appConfig,
35+
private readonly OCMDiscoveryService $ocmDiscoveryService,
3736
private LoggerInterface $logger,
3837
) {
3938
parent::__construct('core', $request);
@@ -56,29 +55,16 @@ public function __construct(
5655
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
5756
public function discovery(): DataResponse {
5857
try {
59-
$cap = Server::get(
60-
$this->appConfig->getValueString(
61-
'core', 'ocm_providers',
62-
Capabilities::class,
63-
lazy: true
64-
)
65-
);
66-
67-
if (!($cap instanceof ICapability)) {
68-
throw new Exception('loaded class does not implements OCP\Capabilities\ICapability');
69-
}
70-
7158
return new DataResponse(
72-
$cap->getCapabilities()['ocm'] ?? ['enabled' => false],
59+
$this->ocmDiscoveryService->getLocalOCMProvider()->jsonSerialize(),
7360
Http::STATUS_OK,
7461
[
7562
'X-NEXTCLOUD-OCM-PROVIDERS' => true,
7663
'Content-Type' => 'application/json'
7764
]
7865
);
79-
} catch (ContainerExceptionInterface|Exception $e) {
66+
} catch (Exception $e) {
8067
$this->logger->error('issue during OCM discovery request', ['exception' => $e]);
81-
8268
return new DataResponse(
8369
['message' => '/ocm-provider/ not supported'],
8470
Http::STATUS_INTERNAL_SERVER_ERROR

lib/private/OCM/Model/OCMProvider.php

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
namespace OC\OCM\Model;
1111

1212
use NCU\Security\Signature\Model\Signatory;
13-
use OCP\EventDispatcher\IEventDispatcher;
14-
use OCP\IConfig;
15-
use OCP\OCM\Events\ResourceTypeRegisterEvent;
1613
use OCP\OCM\Exceptions\OCMArgumentException;
1714
use OCP\OCM\Exceptions\OCMProviderException;
1815
use OCP\OCM\ICapabilityAwareOCMProvider;
@@ -22,7 +19,6 @@
2219
* @since 28.0.0
2320
*/
2421
class OCMProvider implements ICapabilityAwareOCMProvider {
25-
private string $provider;
2622
private bool $enabled = false;
2723
private string $apiVersion = '';
2824
private string $inviteAcceptDialog = '';
@@ -31,13 +27,10 @@ class OCMProvider implements ICapabilityAwareOCMProvider {
3127
/** @var IOCMResource[] */
3228
private array $resourceTypes = [];
3329
private ?Signatory $signatory = null;
34-
private bool $emittedEvent = false;
3530

3631
public function __construct(
37-
protected IEventDispatcher $dispatcher,
38-
protected IConfig $config,
32+
private readonly string $provider = '',
3933
) {
40-
$this->provider = 'Nextcloud ' . $config->getSystemValue('version');
4134
}
4235

4336
/**
@@ -180,12 +173,6 @@ public function setResourceTypes(array $resourceTypes): static {
180173
* @return IOCMResource[]
181174
*/
182175
public function getResourceTypes(): array {
183-
if (!$this->emittedEvent) {
184-
$this->emittedEvent = true;
185-
$event = new ResourceTypeRegisterEvent($this);
186-
$this->dispatcher->dispatchTyped($event);
187-
}
188-
189176
return $this->resourceTypes;
190177
}
191178

lib/private/OCM/OCMDiscoveryService.php

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,19 @@
1111

1212
use GuzzleHttp\Exception\ConnectException;
1313
use JsonException;
14+
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
15+
use NCU\Security\Signature\Exceptions\SignatoryException;
16+
use OC\Core\AppInfo\ConfigLexicon;
17+
use OC\OCM\Model\OCMProvider;
1418
use OCP\AppFramework\Http;
19+
use OCP\EventDispatcher\IEventDispatcher;
1520
use OCP\Http\Client\IClientService;
21+
use OCP\IAppConfig;
1622
use OCP\ICache;
1723
use OCP\ICacheFactory;
1824
use OCP\IConfig;
25+
use OCP\IURLGenerator;
26+
use OCP\OCM\Events\ResourceTypeRegisterEvent;
1927
use OCP\OCM\Exceptions\OCMProviderException;
2028
use OCP\OCM\ICapabilityAwareOCMProvider;
2129
use OCP\OCM\IOCMDiscoveryService;
@@ -26,18 +34,25 @@
2634
*/
2735
class OCMDiscoveryService implements IOCMDiscoveryService {
2836
private ICache $cache;
37+
public const API_VERSION = '1.1.0';
38+
39+
private ?ICapabilityAwareOCMProvider $localProvider = null;
40+
/** @var array<string, ICapabilityAwareOCMProvider> */
41+
private array $remoteProviders = [];
2942

3043
public function __construct(
3144
ICacheFactory $cacheFactory,
3245
private IClientService $clientService,
33-
private IConfig $config,
34-
private ICapabilityAwareOCMProvider $provider,
46+
private IEventDispatcher $eventDispatcher,
47+
protected IConfig $config,
48+
private IAppConfig $appConfig,
49+
private IURLGenerator $urlGenerator,
50+
private OCMSignatoryManager $ocmSignatoryManager,
3551
private LoggerInterface $logger,
3652
) {
3753
$this->cache = $cacheFactory->createDistributed('ocm-discovery');
3854
}
3955

40-
4156
/**
4257
* @param string $remote
4358
* @param bool $skipCache
@@ -56,17 +71,24 @@ public function discover(string $remote, bool $skipCache = false): ICapabilityAw
5671
}
5772
}
5873

74+
if (array_key_exists($remote, $this->remoteProviders)) {
75+
return $this->remoteProviders[$remote];
76+
}
77+
78+
$provider = new OCMProvider();
79+
5980
if (!$skipCache) {
6081
try {
6182
$cached = $this->cache->get($remote);
6283
if ($cached === false) {
6384
throw new OCMProviderException('Previous discovery failed.');
6485
}
6586

66-
$this->provider->import(json_decode($cached ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
67-
return $this->provider;
87+
$provider->import(json_decode($cached ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
88+
$this->remoteProviders[$remote] = $provider;
89+
return $provider;
6890
} catch (JsonException|OCMProviderException $e) {
69-
// we ignore cache on issues
91+
$this->logger->warning('cache issue on ocm discovery', ['exception' => $e]);
7092
}
7193
}
7294

@@ -81,15 +103,20 @@ public function discover(string $remote, bool $skipCache = false): ICapabilityAw
81103
}
82104
$response = $client->get($remote . '/ocm-provider/', $options);
83105

106+
$body = null;
84107
if ($response->getStatusCode() === Http::STATUS_OK) {
85108
$body = $response->getBody();
86109
// update provider with data returned by the request
87-
$this->provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []);
110+
$provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []);
88111
$this->cache->set($remote, $body, 60 * 60 * 24);
112+
$this->remoteProviders[$remote] = $provider;
113+
return $provider;
89114
}
90-
} catch (JsonException|OCMProviderException $e) {
115+
116+
throw new OCMProviderException('invalid remote ocm endpoint');
117+
} catch (JsonException|OCMProviderException) {
91118
$this->cache->set($remote, false, 5 * 60);
92-
throw new OCMProviderException('data returned by remote seems invalid - ' . ($body ?? ''));
119+
throw new OCMProviderException('data returned by remote seems invalid - status:' . $response->getStatusCode() . ' - ' . ($body ?? ''));
93120
} catch (\Exception $e) {
94121
$this->cache->set($remote, false, 5 * 60);
95122
$this->logger->warning('error while discovering ocm provider', [
@@ -98,7 +125,61 @@ public function discover(string $remote, bool $skipCache = false): ICapabilityAw
98125
]);
99126
throw new OCMProviderException('error while requesting remote ocm provider');
100127
}
128+
}
101129

102-
return $this->provider;
130+
/**
131+
* @return ICapabilityAwareOCMProvider
132+
*/
133+
public function getLocalOCMProvider(bool $fullDetails = true): ICapabilityAwareOCMProvider {
134+
if ($this->localProvider !== null) {
135+
return $this->localProvider;
136+
}
137+
138+
$provider = new OCMProvider('Nextcloud ' . $this->config->getSystemValue('version'));
139+
if (!$this->appConfig->getValueBool('core', ConfigLexicon::OCM_DISCOVERY_ENABLED)) {
140+
return $provider;
141+
}
142+
143+
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
144+
$pos = strrpos($url, '/');
145+
if ($pos === false) {
146+
$this->logger->debug('generated route should contain a slash character');
147+
return $provider;
148+
}
149+
150+
$provider->setEnabled(true);
151+
$provider->setApiVersion(self::API_VERSION);
152+
$provider->setEndPoint(substr($url, 0, $pos));
153+
$provider->setCapabilities(['/invite-accepted', '/notifications', '/shares']);
154+
155+
$resource = $provider->createNewResourceType();
156+
$resource->setName('file')
157+
->setShareTypes(['user', 'group'])
158+
->setProtocols(['webdav' => '/public.php/webdav/']);
159+
$provider->addResourceType($resource);
160+
161+
if ($fullDetails) {
162+
// Adding a public key to the ocm discovery
163+
try {
164+
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
165+
/**
166+
* @experimental 31.0.0
167+
* @psalm-suppress UndefinedInterfaceMethod
168+
*/
169+
$provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
170+
} else {
171+
$this->logger->debug('ocm public key feature disabled');
172+
}
173+
} catch (SignatoryException|IdentityNotFoundException $e) {
174+
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
175+
}
176+
}
177+
178+
$event = new ResourceTypeRegisterEvent($provider);
179+
$this->eventDispatcher->dispatchTyped($event);
180+
181+
$this->localProvider = $provider;
182+
return $provider;
103183
}
184+
104185
}

lib/private/OCM/OCMSignatoryManager.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
use OCP\IAppConfig;
2121
use OCP\IURLGenerator;
2222
use OCP\OCM\Exceptions\OCMProviderException;
23+
use OCP\Server;
24+
use Psr\Container\ContainerExceptionInterface;
25+
use Psr\Container\NotFoundExceptionInterface;
2326
use Psr\Log\LoggerInterface;
2427

2528
/**
@@ -41,7 +44,6 @@ public function __construct(
4144
private readonly ISignatureManager $signatureManager,
4245
private readonly IURLGenerator $urlGenerator,
4346
private readonly Manager $identityProofManager,
44-
private readonly OCMDiscoveryService $ocmDiscoveryService,
4547
private readonly LoggerInterface $logger,
4648
) {
4749
}
@@ -144,15 +146,15 @@ private function generateKeyId(): string {
144146
*/
145147
public function getRemoteSignatory(string $remote): ?Signatory {
146148
try {
147-
$ocmProvider = $this->ocmDiscoveryService->discover($remote, true);
149+
$ocmProvider = Server::get(OCMDiscoveryService::class)->discover($remote, true);
148150
/**
149151
* @experimental 31.0.0
150152
* @psalm-suppress UndefinedInterfaceMethod
151153
*/
152154
$signatory = $ocmProvider->getSignatory();
153155
$signatory?->setSignatoryType(SignatoryType::TRUSTED);
154156
return $signatory;
155-
} catch (OCMProviderException $e) {
157+
} catch (NotFoundExceptionInterface|ContainerExceptionInterface|OCMProviderException $e) {
156158
$this->logger->warning('fail to get remote signatory', ['exception' => $e, 'remote' => $remote]);
157159
return null;
158160
}

0 commit comments

Comments
 (0)