diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2023-09-05 11:42:48 -0100 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2023-09-20 08:23:45 -0100 |
commit | 8b9e7e235dd7711bba31441e9a8b09dc33f85d38 (patch) | |
tree | 3d0d2725d819cfc53c73d198681e8d00178d4a64 /lib | |
parent | d5fc95b661d34899095dc53b363cdb35fa458565 (diff) | |
download | nextcloud-server-8b9e7e235dd7711bba31441e9a8b09dc33f85d38.tar.gz nextcloud-server-8b9e7e235dd7711bba31441e9a8b09dc33f85d38.zip |
ocm controller
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 9 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 9 | ||||
-rw-r--r-- | lib/private/Federation/CloudFederationProviderManager.php | 100 | ||||
-rw-r--r-- | lib/private/OCM/Model/OCMProvider.php | 211 | ||||
-rw-r--r-- | lib/private/OCM/Model/OCMResource.php | 125 | ||||
-rw-r--r-- | lib/private/OCM/OCMDiscoveryService.php | 138 | ||||
-rw-r--r-- | lib/private/Server.php | 5 | ||||
-rw-r--r-- | lib/public/OCM/Exceptions/OCMArgumentException.php | 34 | ||||
-rw-r--r-- | lib/public/OCM/Exceptions/OCMProviderException.php | 34 | ||||
-rw-r--r-- | lib/public/OCM/IOCMDiscoveryService.php | 48 | ||||
-rw-r--r-- | lib/public/OCM/IOCMProvider.php | 143 | ||||
-rw-r--r-- | lib/public/OCM/IOCMResource.php | 99 |
12 files changed, 882 insertions, 73 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index fb063a82088..c2d9d153673 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -536,6 +536,11 @@ return array( 'OCP\\Notification\\IManager' => $baseDir . '/lib/public/Notification/IManager.php', 'OCP\\Notification\\INotification' => $baseDir . '/lib/public/Notification/INotification.php', 'OCP\\Notification\\INotifier' => $baseDir . '/lib/public/Notification/INotifier.php', + 'OCP\\OCM\\Exceptions\\OCMArgumentException' => $baseDir . '/lib/public/OCM/Exceptions/OCMArgumentException.php', + 'OCP\\OCM\\Exceptions\\OCMProviderException' => $baseDir . '/lib/public/OCM/Exceptions/OCMProviderException.php', + 'OCP\\OCM\\IOCMDiscoveryService' => $baseDir . '/lib/public/OCM/IOCMDiscoveryService.php', + 'OCP\\OCM\\IOCMProvider' => $baseDir . '/lib/public/OCM/IOCMProvider.php', + 'OCP\\OCM\\IOCMResource' => $baseDir . '/lib/public/OCM/IOCMResource.php', 'OCP\\OCS\\IDiscoveryService' => $baseDir . '/lib/public/OCS/IDiscoveryService.php', 'OCP\\PreConditionNotMetException' => $baseDir . '/lib/public/PreConditionNotMetException.php', 'OCP\\Preview\\BeforePreviewFetchedEvent' => $baseDir . '/lib/public/Preview/BeforePreviewFetchedEvent.php', @@ -1074,6 +1079,7 @@ return array( 'OC\\Core\\Controller\\LostController' => $baseDir . '/core/Controller/LostController.php', 'OC\\Core\\Controller\\NavigationController' => $baseDir . '/core/Controller/NavigationController.php', 'OC\\Core\\Controller\\OCJSController' => $baseDir . '/core/Controller/OCJSController.php', + 'OC\\Core\\Controller\\OCMController' => $baseDir . '/core/Controller/OCMController.php', 'OC\\Core\\Controller\\OCSController' => $baseDir . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => $baseDir . '/core/Controller/PreviewController.php', 'OC\\Core\\Controller\\ProfileApiController' => $baseDir . '/core/Controller/ProfileApiController.php', @@ -1463,6 +1469,9 @@ return array( 'OC\\Notification\\Action' => $baseDir . '/lib/private/Notification/Action.php', 'OC\\Notification\\Manager' => $baseDir . '/lib/private/Notification/Manager.php', 'OC\\Notification\\Notification' => $baseDir . '/lib/private/Notification/Notification.php', + 'OC\\OCM\\Model\\OCMProvider' => $baseDir . '/lib/private/OCM/Model/OCMProvider.php', + 'OC\\OCM\\Model\\OCMResource' => $baseDir . '/lib/private/OCM/Model/OCMResource.php', + 'OC\\OCM\\OCMDiscoveryService' => $baseDir . '/lib/private/OCM/OCMDiscoveryService.php', 'OC\\OCS\\CoreCapabilities' => $baseDir . '/lib/private/OCS/CoreCapabilities.php', 'OC\\OCS\\DiscoveryService' => $baseDir . '/lib/private/OCS/DiscoveryService.php', 'OC\\OCS\\Exception' => $baseDir . '/lib/private/OCS/Exception.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 35b2318c4b1..73a90620605 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -569,6 +569,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Notification\\IManager' => __DIR__ . '/../../..' . '/lib/public/Notification/IManager.php', 'OCP\\Notification\\INotification' => __DIR__ . '/../../..' . '/lib/public/Notification/INotification.php', 'OCP\\Notification\\INotifier' => __DIR__ . '/../../..' . '/lib/public/Notification/INotifier.php', + 'OCP\\OCM\\Exceptions\\OCMArgumentException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMArgumentException.php', + 'OCP\\OCM\\Exceptions\\OCMProviderException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMProviderException.php', + 'OCP\\OCM\\IOCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMDiscoveryService.php', + 'OCP\\OCM\\IOCMProvider' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMProvider.php', + 'OCP\\OCM\\IOCMResource' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMResource.php', 'OCP\\OCS\\IDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCS/IDiscoveryService.php', 'OCP\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/PreConditionNotMetException.php', 'OCP\\Preview\\BeforePreviewFetchedEvent' => __DIR__ . '/../../..' . '/lib/public/Preview/BeforePreviewFetchedEvent.php', @@ -1107,6 +1112,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Controller\\LostController' => __DIR__ . '/../../..' . '/core/Controller/LostController.php', 'OC\\Core\\Controller\\NavigationController' => __DIR__ . '/../../..' . '/core/Controller/NavigationController.php', 'OC\\Core\\Controller\\OCJSController' => __DIR__ . '/../../..' . '/core/Controller/OCJSController.php', + 'OC\\Core\\Controller\\OCMController' => __DIR__ . '/../../..' . '/core/Controller/OCMController.php', 'OC\\Core\\Controller\\OCSController' => __DIR__ . '/../../..' . '/core/Controller/OCSController.php', 'OC\\Core\\Controller\\PreviewController' => __DIR__ . '/../../..' . '/core/Controller/PreviewController.php', 'OC\\Core\\Controller\\ProfileApiController' => __DIR__ . '/../../..' . '/core/Controller/ProfileApiController.php', @@ -1496,6 +1502,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Notification\\Action' => __DIR__ . '/../../..' . '/lib/private/Notification/Action.php', 'OC\\Notification\\Manager' => __DIR__ . '/../../..' . '/lib/private/Notification/Manager.php', 'OC\\Notification\\Notification' => __DIR__ . '/../../..' . '/lib/private/Notification/Notification.php', + 'OC\\OCM\\Model\\OCMProvider' => __DIR__ . '/../../..' . '/lib/private/OCM/Model/OCMProvider.php', + 'OC\\OCM\\Model\\OCMResource' => __DIR__ . '/../../..' . '/lib/private/OCM/Model/OCMResource.php', + 'OC\\OCM\\OCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCM/OCMDiscoveryService.php', 'OC\\OCS\\CoreCapabilities' => __DIR__ . '/../../..' . '/lib/private/OCS/CoreCapabilities.php', 'OC\\OCS\\DiscoveryService' => __DIR__ . '/../../..' . '/lib/private/OCS/DiscoveryService.php', 'OC\\OCS\\Exception' => __DIR__ . '/../../..' . '/lib/private/OCS/Exception.php', diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php index b11c4060ab4..ea2f0dd7575 100644 --- a/lib/private/Federation/CloudFederationProviderManager.php +++ b/lib/private/Federation/CloudFederationProviderManager.php @@ -1,9 +1,13 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org> * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * * @license GNU AGPL version 3 or any later version * @@ -32,6 +36,9 @@ use OCP\Federation\ICloudFederationProviderManager; use OCP\Federation\ICloudFederationShare; use OCP\Federation\ICloudIdManager; use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\IOCMDiscoveryService; use Psr\Log\LoggerInterface; /** @@ -43,40 +50,16 @@ use Psr\Log\LoggerInterface; */ class CloudFederationProviderManager implements ICloudFederationProviderManager { /** @var array list of available cloud federation providers */ - private $cloudFederationProvider; - - /** @var IAppManager */ - private $appManager; - - /** @var IClientService */ - private $httpClientService; - - /** @var ICloudIdManager */ - private $cloudIdManager; - - private LoggerInterface $logger; - - /** @var array cache OCM end-points */ - private $ocmEndPoints = []; - - private $supportedAPIVersion = '1.0-proposal1'; - - /** - * CloudFederationProviderManager constructor. - * - * @param IAppManager $appManager - * @param IClientService $httpClientService - * @param ICloudIdManager $cloudIdManager - */ - public function __construct(IAppManager $appManager, - IClientService $httpClientService, - ICloudIdManager $cloudIdManager, - LoggerInterface $logger) { - $this->cloudFederationProvider = []; - $this->appManager = $appManager; - $this->httpClientService = $httpClientService; - $this->cloudIdManager = $cloudIdManager; - $this->logger = $logger; + private array $cloudFederationProvider = []; + + public function __construct( + private IConfig $config, + private IAppManager $appManager, + private IClientService $httpClientService, + private ICloudIdManager $cloudIdManager, + private IOCMDiscoveryService $discoveryService, + private LoggerInterface $logger + ) { } @@ -130,16 +113,18 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager public function sendShare(ICloudFederationShare $share) { $cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith()); - $ocmEndPoint = $this->getOCMEndPoint($cloudID->getRemote()); - if (empty($ocmEndPoint)) { + try { + $ocmProvider = $this->discoveryService->discover($cloudID->getRemote()); + } catch (OCMProviderException $e) { return false; } $client = $this->httpClientService->newClient(); try { - $response = $client->post($ocmEndPoint . '/shares', [ + $response = $client->post($ocmProvider->getEndPoint() . '/shares', [ 'body' => json_encode($share->getShare()), 'headers' => ['content-type' => 'application/json'], + 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false), 'timeout' => 10, 'connect_timeout' => 10, ]); @@ -168,17 +153,18 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager * @return array|false */ public function sendNotification($url, ICloudFederationNotification $notification) { - $ocmEndPoint = $this->getOCMEndPoint($url); - - if (empty($ocmEndPoint)) { + try { + $ocmProvider = $this->discoveryService->discover($url); + } catch (OCMProviderException $e) { return false; } $client = $this->httpClientService->newClient(); try { - $response = $client->post($ocmEndPoint . '/notifications', [ + $response = $client->post($ocmProvider->getEndPoint() . '/notifications', [ 'body' => json_encode($notification->getMessage()), 'headers' => ['content-type' => 'application/json'], + 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false), 'timeout' => 10, 'connect_timeout' => 10, ]); @@ -202,36 +188,4 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager public function isReady() { return $this->appManager->isEnabledForUser('cloud_federation_api'); } - /** - * check if server supports the new OCM api and ask for the correct end-point - * - * @param string $url full base URL of the cloud server - * @return string - */ - protected function getOCMEndPoint($url) { - if (isset($this->ocmEndPoints[$url])) { - return $this->ocmEndPoints[$url]; - } - - $client = $this->httpClientService->newClient(); - try { - $response = $client->get($url . '/ocm-provider/', ['timeout' => 10, 'connect_timeout' => 10]); - } catch (\Exception $e) { - $this->ocmEndPoints[$url] = ''; - return ''; - } - - $result = $response->getBody(); - $result = json_decode($result, true); - - $supportedVersion = isset($result['apiVersion']) && $result['apiVersion'] === $this->supportedAPIVersion; - - if (isset($result['endPoint']) && $supportedVersion) { - $this->ocmEndPoints[$url] = $result['endPoint']; - return $result['endPoint']; - } - - $this->ocmEndPoints[$url] = ''; - return ''; - } } diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php new file mode 100644 index 00000000000..40ef43085b3 --- /dev/null +++ b/lib/private/OCM/Model/OCMProvider.php @@ -0,0 +1,211 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\OCM\Model; + +use JsonSerializable; +use OCP\OCM\Exceptions\OCMArgumentException; +use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\IOCMProvider; + +/** + * @since 28.0.0 + */ +class OCMProvider implements IOCMProvider, JsonSerializable { + private bool $enabled = false; + private string $apiVersion = ''; + private string $endPoint = ''; + /** @var OCMResource[] */ + private array $resourceTypes = []; + + /** + * @param bool $enabled + * + * @return OCMProvider + */ + public function setEnabled(bool $enabled): self { + $this->enabled = $enabled; + + return $this; + } + + /** + * @return bool + */ + public function isEnabled(): bool { + return $this->enabled; + } + + /** + * @param string $apiVersion + * + * @return OCMProvider + */ + public function setApiVersion(string $apiVersion): self { + $this->apiVersion = $apiVersion; + + return $this; + } + + /** + * @return string + */ + public function getApiVersion(): string { + return $this->apiVersion; + } + + /** + * @param string $endPoint + * + * @return OCMProvider + */ + public function setEndPoint(string $endPoint): self { + $this->endPoint = $endPoint; + + return $this; + } + + /** + * @return string + */ + public function getEndPoint(): string { + return $this->endPoint; + } + + /** + * @param OCMResource $resource + * + * @return $this + */ + public function addResourceType(OCMResource $resource): self { + $this->resourceTypes[] = $resource; + + return $this; + } + + /** + * @param OCMResource[] $resourceTypes + * + * @return OCMProvider + */ + public function setResourceTypes(array $resourceTypes): self { + $this->resourceTypes = $resourceTypes; + + return $this; + } + + /** + * @return OCMResource[] + */ + public function getResourceTypes(): array { + return $this->resourceTypes; + } + + /** + * @param string $resourceName + * @param string $protocol + * + * @return string + * @throws OCMArgumentException + */ + public function extractProtocolEntry(string $resourceName, string $protocol): string { + foreach ($this->getResourceTypes() as $resource) { + if ($resource->getName() === $resourceName) { + $entry = $resource->getProtocols()[$protocol] ?? null; + if (is_null($entry)) { + throw new OCMArgumentException('protocol not found'); + } + + return (string)$entry; + } + } + + throw new OCMArgumentException('resource not found'); + } + + /** + * import data from an array + * + * @param array $data + * + * @return self + * @throws OCMProviderException in case a descent provider cannot be generated from data + * @see self::jsonSerialize() + */ + public function import(array $data): self { + $this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false) + ->setApiVersion((string)($data['apiVersion'] ?? '')) + ->setEndPoint($data['endPoint'] ?? ''); + + $resources = []; + foreach (($data['resourceTypes'] ?? []) as $resourceData) { + $resource = new OCMResource(); + $resources[] = $resource->import($resourceData); + } + $this->setResourceTypes($resources); + + if (!$this->looksValid()) { + throw new OCMProviderException('remote provider does not look valid'); + } + + return $this; + } + + + /** + * @return bool + */ + private function looksValid(): bool { + return ($this->getApiVersion() !== '' && $this->getEndPoint() !== ''); + } + + + /** + * @return array{ + * enabled: bool, + * apiVersion: string, + * endPoint: string, + * resourceTypes: array{ + * name: string, + * shareTypes: string[], + * protocols: array<string, string> + * }[] + * } + */ + public function jsonSerialize(): array { + $resourceTypes = []; // this is needed for psalm + foreach ($this->getResourceTypes() as $res) { + $resourceTypes[] = $res->jsonSerialize(); + } + + return [ + 'enabled' => $this->isEnabled(), + 'apiVersion' => $this->getApiVersion(), + 'endPoint' => $this->getEndPoint(), + 'resourceTypes' => $resourceTypes + ]; + } +} diff --git a/lib/private/OCM/Model/OCMResource.php b/lib/private/OCM/Model/OCMResource.php new file mode 100644 index 00000000000..12948aa8949 --- /dev/null +++ b/lib/private/OCM/Model/OCMResource.php @@ -0,0 +1,125 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\OCM\Model; + +use JsonSerializable; +use OCP\OCM\IOCMResource; + +/** + * @since 28.0.0 + */ +class OCMResource implements IOCMResource, JsonSerializable { + private string $name = ''; + /** @var string[] */ + private array $shareTypes = []; + /** @var array<string, string> */ + private array $protocols = []; + + /** + * @param string $name + * + * @return OCMResource + */ + public function setName(string $name): self { + $this->name = $name; + + return $this; + } + + /** + * @return string + */ + public function getName(): string { + return $this->name; + } + + /** + * @param string[] $shareTypes + * + * @return OCMResource + */ + public function setShareTypes(array $shareTypes): self { + $this->shareTypes = $shareTypes; + + return $this; + } + + /** + * @return string[] + */ + public function getShareTypes(): array { + return $this->shareTypes; + } + + /** + * @param array<string, string> $protocols + * + * @return $this + */ + public function setProtocols(array $protocols): self { + $this->protocols = $protocols; + + return $this; + } + + /** + * @return array<string, string> + */ + public function getProtocols(): array { + return $this->protocols; + } + + /** + * import data from an array + * + * @param array $data + * + * @return self + * @see self::jsonSerialize() + */ + public function import(array $data): self { + return $this->setName((string)($data['name'] ?? '')) + ->setShareTypes($data['shareTypes'] ?? []) + ->setProtocols($data['protocols'] ?? []); + } + + /** + * + * @return array{ + * name: string, + * shareTypes: string[], + * protocols: array<string, string> + * } + */ + public function jsonSerialize(): array { + return [ + 'name' => $this->getName(), + 'shareTypes' => $this->getShareTypes(), + 'protocols' => $this->getProtocols() + ]; + } +} diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php new file mode 100644 index 00000000000..e3b1d350813 --- /dev/null +++ b/lib/private/OCM/OCMDiscoveryService.php @@ -0,0 +1,138 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\OCM; + +use JsonException; +use OC\OCM\Model\OCMProvider; +use OCP\AppFramework\Http; +use OCP\Http\Client\IClientService; +use OCP\ICache; +use OCP\ICacheFactory; +use OCP\IConfig; +use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\IOCMDiscoveryService; +use OCP\OCM\IOCMProvider; +use Psr\Log\LoggerInterface; + +/** + * @since 28.0.0 + */ +class OCMDiscoveryService implements IOCMDiscoveryService { + private ICache $cache; + private array $supportedAPIVersion = + [ + '1.0-proposal1', + '1.0', + '1.1' + ]; + + public function __construct( + ICacheFactory $cacheFactory, + private IClientService $clientService, + private IConfig $config, + private LoggerInterface $logger + ) { + $this->cache = $cacheFactory->createDistributed('ocm-discovery'); + } + + + /** + * @param string $remote + * @param bool $skipCache + * + * @return IOCMProvider + * @throws OCMProviderException + */ + public function discover(string $remote, bool $skipCache = false): IOCMProvider { + $remote = rtrim($remote, '/'); + $provider = new OCMProvider(); + + if (!$skipCache) { + try { + $provider->import(json_decode($this->cache->get($remote) ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []); + if ($this->supportedAPIVersion($provider->getApiVersion())) { + return $provider; // if cache looks valid, we use it + } + } catch (JsonException|OCMProviderException $e) { + // we ignore cache on issues + } + } + + $client = $this->clientService->newClient(); + try { + $response = $client->get( + $remote . '/ocm-provider/', + [ + 'timeout' => 10, + 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates'), + 'connect_timeout' => 10, + ] + ); + + if ($response->getStatusCode() === Http::STATUS_OK) { + $body = $response->getBody(); + // update provider with data returned by the request + $provider->import(json_decode($body, true, 8, JSON_THROW_ON_ERROR) ?? []); + $this->cache->set($remote, $body, 60 * 60 * 24); + } + } catch (JsonException|OCMProviderException $e) { + throw new OCMProviderException('data returned by remote seems invalid - ' . ($body ?? '')); + } catch (\Exception $e) { + $this->logger->warning('error while discovering ocm provider', [ + 'exception' => $e, + 'remote' => $remote + ]); + throw new OCMProviderException('error while requesting remote ocm provider'); + } + + if (!$this->supportedAPIVersion($provider->getApiVersion())) { + throw new OCMProviderException('API version not supported'); + } + + return $provider; + } + + /** + * Check the version from remote is supported. + * The minor version of the API will be ignored: + * 1.0.1 is identified as 1.0 + * + * @param string $version + * + * @return bool + */ + private function supportedAPIVersion(string $version): bool { + $dot1 = strpos($version, '.'); + $dot2 = strpos($version, '.', $dot1 + 1); + + if ($dot2 > 0) { + $version = substr($version, 0, $dot2); + } + + return (in_array($version, $this->supportedAPIVersion)); + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index ba8b18f9a05..40e5cabbbcc 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -124,6 +124,7 @@ use OC\Metadata\Capabilities as MetadataCapabilities; use OC\Metadata\IMetadataManager; use OC\Metadata\MetadataManager; use OC\Notification\Manager; +use OC\OCM\OCMDiscoveryService; use OC\OCS\DiscoveryService; use OC\Preview\GeneratorHelper; use OC\Preview\IMagickSupport; @@ -228,6 +229,7 @@ use OCP\Lock\ILockingProvider; use OCP\Lockdown\ILockdownManager; use OCP\Log\ILogFactory; use OCP\Mail\IMailer; +use OCP\OCM\IOCMDiscoveryService; use OCP\Remote\Api\IApiFactory; use OCP\Remote\IInstanceFactory; use OCP\RichObjectStrings\IValidator; @@ -1306,6 +1308,7 @@ class Server extends ServerContainer implements IServerContainer { $c->get(IClientService::class) ); }); + $this->registerAlias(IOCMDiscoveryService::class, OCMDiscoveryService::class); $this->registerService(ICloudIdManager::class, function (ContainerInterface $c) { return new CloudIdManager( @@ -1321,9 +1324,11 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService(ICloudFederationProviderManager::class, function (ContainerInterface $c) { return new CloudFederationProviderManager( + $c->get(\OCP\IConfig::class), $c->get(IAppManager::class), $c->get(IClientService::class), $c->get(ICloudIdManager::class), + $c->get(IOCMDiscoveryService::class), $c->get(LoggerInterface::class) ); }); diff --git a/lib/public/OCM/Exceptions/OCMArgumentException.php b/lib/public/OCM/Exceptions/OCMArgumentException.php new file mode 100644 index 00000000000..e3abd7bf26b --- /dev/null +++ b/lib/public/OCM/Exceptions/OCMArgumentException.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OCP\OCM\Exceptions; + +use Exception; + +/** + * @since 28.0.0 + */ +class OCMArgumentException extends Exception { +} diff --git a/lib/public/OCM/Exceptions/OCMProviderException.php b/lib/public/OCM/Exceptions/OCMProviderException.php new file mode 100644 index 00000000000..32dab10dc68 --- /dev/null +++ b/lib/public/OCM/Exceptions/OCMProviderException.php @@ -0,0 +1,34 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright 2023, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OCP\OCM\Exceptions; + +use Exception; + +/** + * @since 28.0.0 + */ +class OCMProviderException extends Exception { +} diff --git a/lib/public/OCM/IOCMDiscoveryService.php b/lib/public/OCM/IOCMDiscoveryService.php new file mode 100644 index 00000000000..2407e7b24e8 --- /dev/null +++ b/lib/public/OCM/IOCMDiscoveryService.php @@ -0,0 +1,48 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\OCM; + +use OCP\OCM\Exceptions\OCMProviderException; + +/** + * Discover remote OCM services + * + * @since 28.0.0 + */ +interface IOCMDiscoveryService { + /** + * Discover remote OCM services + * + * @param string $remote address of the remote provider + * @param bool $skipCache ignore cache, refresh data + * + * @return IOCMProvider + * @throws OCMProviderException if no valid discovery data can be returned + * @since 28.0.0 + */ + public function discover(string $remote, bool $skipCache = false): IOCMProvider; +} diff --git a/lib/public/OCM/IOCMProvider.php b/lib/public/OCM/IOCMProvider.php new file mode 100644 index 00000000000..f99ccf1cd23 --- /dev/null +++ b/lib/public/OCM/IOCMProvider.php @@ -0,0 +1,143 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\OCM; + +use OC\OCM\Model\OCMResource; +use OCP\OCM\Exceptions\OCMArgumentException; +use OCP\OCM\Exceptions\OCMProviderException; + +/** + * Model based on the Open Cloud Mesh Discovery API + * @link https://github.com/cs3org/OCM-API/ + * @since 28.0.0 + */ +interface IOCMProvider { + /** + * enable OCM + * + * @param bool $enabled + * + * @return self + * @since 28.0.0 + */ + public function setEnabled(bool $enabled): self; + + /** + * is set as enabled ? + * + * @return bool + * @since 28.0.0 + */ + public function isEnabled(): bool; + + /** + * get set API Version + * + * @param string $apiVersion + * + * @return self + * @since 28.0.0 + */ + public function setApiVersion(string $apiVersion): self; + + /** + * returns API version + * + * @return string + * @since 28.0.0 + */ + public function getApiVersion(): string; + + /** + * configure endpoint + * + * @param string $endPoint + * + * @return self + * @since 28.0.0 + */ + public function setEndPoint(string $endPoint): self; + + /** + * get configured endpoint + * + * @return string + * @since 28.0.0 + */ + public function getEndPoint(): string; + + /** + * add a single resource to the object + * + * @param OCMResource $resource + * + * @return self + * @since 28.0.0 + */ + public function addResourceType(OCMResource $resource): self; + + /** + * set resources + * + * @param OCMResource[] $resourceTypes + * + * @return self + * @since 28.0.0 + */ + public function setResourceTypes(array $resourceTypes): self; + + /** + * get all set resources + * + * @return IOCMResource[] + * @since 28.0.0 + */ + public function getResourceTypes(): array; + + /** + * extract a specific string value from the listing of protocols, based on resource-name and protocol-name + * + * @param string $resourceName + * @param string $protocol + * + * @return string + * @throws OCMArgumentException + * @since 28.0.0 + */ + public function extractProtocolEntry(string $resourceName, string $protocol): string; + + /** + * import data from an array + * + * @param array<string, int|string|bool|array> $data + * + * @return self + * @throws OCMProviderException in case a descent provider cannot be generated from data + * @since 28.0.0 + */ + public function import(array $data): self; +} diff --git a/lib/public/OCM/IOCMResource.php b/lib/public/OCM/IOCMResource.php new file mode 100644 index 00000000000..381af61cecc --- /dev/null +++ b/lib/public/OCM/IOCMResource.php @@ -0,0 +1,99 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2023 Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCP\OCM; + +/** + * Model based on the Open Cloud Mesh Discovery API + * + * @link https://github.com/cs3org/OCM-API/ + * @since 28.0.0 + */ +interface IOCMResource { + /** + * set name of the resource + * + * @param string $name + * + * @return self + * @since 28.0.0 + */ + public function setName(string $name): self; + + /** + * get name of the resource + * + * @return string + * @since 28.0.0 + */ + public function getName(): string; + + /** + * set share types + * + * @param string[] $shareTypes + * + * @return self + * @since 28.0.0 + */ + public function setShareTypes(array $shareTypes): self; + + /** + * get share types + * + * @return string[] + * @since 28.0.0 + */ + public function getShareTypes(): array; + + /** + * set available protocols + * + * @param array<string, string> $protocols + * + * @return self + * @since 28.0.0 + */ + public function setProtocols(array $protocols): self; + + /** + * get configured protocols + * + * @return array<string, string> + * @since 28.0.0 + */ + public function getProtocols(): array; + + /** + * import data from an array + * + * @param array $data + * + * @return self + * @since 28.0.0 + */ + public function import(array $data): self; +} |