diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2023-09-23 14:08:05 -0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-23 14:08:05 -0100 |
commit | b06bff7922846f20ca414f9528d11a47a43fde48 (patch) | |
tree | 5da3977f37f26af41a08b8567f3628f82232907f | |
parent | 6d8cbd5ede73c4679b384b9e45da80f7d41aa865 (diff) | |
parent | 0fac750bcd5eccc65f2861a3bb7633912fb30039 (diff) | |
download | nextcloud-server-b06bff7922846f20ca414f9528d11a47a43fde48.tar.gz nextcloud-server-b06bff7922846f20ca414f9528d11a47a43fde48.zip |
Merge pull request #40586 from nextcloud/backport/39574/39574-stable27
[stable27] ocm services
26 files changed, 1243 insertions, 250 deletions
diff --git a/apps/cloud_federation_api/appinfo/routes.php b/apps/cloud_federation_api/appinfo/routes.php index d70b06f821c..966ff2ce3a1 100644 --- a/apps/cloud_federation_api/appinfo/routes.php +++ b/apps/cloud_federation_api/appinfo/routes.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright Copyright (c) 2020 Joas Schilling <coding@schilljs.com> * * @author Joas Schilling <coding@schilljs.com> + * @author Maxence Lange <maxence@artificial-owl.com> * * @license GNU AGPL version 3 or any later version * @@ -27,15 +28,21 @@ return [ 'routes' => [ [ 'name' => 'RequestHandler#addShare', - 'url' => '/ocm/shares', + 'url' => '/shares', 'verb' => 'POST', - 'root' => '', + 'root' => '/ocm', ], [ 'name' => 'RequestHandler#receiveNotification', - 'url' => '/ocm/notifications', + 'url' => '/notifications', 'verb' => 'POST', - 'root' => '', + 'root' => '/ocm', ], +// [ +// 'name' => 'RequestHandler#inviteAccepted', +// 'url' => '/invite-accepted', +// 'verb' => 'POST', +// 'root' => '/ocm', +// ] ], ]; diff --git a/apps/cloud_federation_api/lib/Capabilities.php b/apps/cloud_federation_api/lib/Capabilities.php index 6164c0e0dba..60d6186b840 100644 --- a/apps/cloud_federation_api/lib/Capabilities.php +++ b/apps/cloud_federation_api/lib/Capabilities.php @@ -1,4 +1,7 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org> * @@ -20,45 +23,61 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\CloudFederationAPI; +use OC\OCM\Model\OCMProvider; +use OC\OCM\Model\OCMResource; use OCP\Capabilities\ICapability; use OCP\IURLGenerator; +use OCP\OCM\Exceptions\OCMArgumentException; class Capabilities implements ICapability { - /** @var IURLGenerator */ - private $urlGenerator; + public const API_VERSION = '1.0-proposal1'; - public function __construct(IURLGenerator $urlGenerator) { - $this->urlGenerator = $urlGenerator; + public function __construct( + private IURLGenerator $urlGenerator, + ) { } /** * Function an app uses to return the capabilities * - * @return array Array containing the apps capabilities - * @since 8.2.0 + * @return array{ + * ocm: array{ + * enabled: bool, + * apiVersion: string, + * endPoint: string, + * resourceTypes: array{ + * name: string, + * shareTypes: string[], + * protocols: array<string, string> + * }[], + * }, + * } + * @throws OCMArgumentException */ public function getCapabilities() { $url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare'); - $capabilities = ['ocm' => - [ - 'enabled' => true, - 'apiVersion' => '1.0-proposal1', - 'endPoint' => substr($url, 0, strrpos($url, '/')), - 'resourceTypes' => [ - [ - 'name' => 'file', - 'shareTypes' => ['user', 'group'], - 'protocols' => [ - 'webdav' => '/public.php/webdav/', - ] - ], - ] - ] - ]; + $provider = new OCMProvider(); + $provider->setEnabled(true); + $provider->setApiVersion(self::API_VERSION); + + $pos = strrpos($url, '/'); + if (false === $pos) { + throw new OCMArgumentException('generated route should contains a slash character'); + } + + $provider->setEndPoint(substr($url, 0, $pos)); + + $resource = new OCMResource(); + $resource->setName('file') + ->setShareTypes(['user', 'group']) + ->setProtocols(['webdav' => '/public.php/webdav/']); + + $provider->setResourceTypes([$resource]); - return $capabilities; + return ['ocm' => $provider->jsonSerialize()]; } } diff --git a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php index ef77f2fa317..fd55746a83c 100644 --- a/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php +++ b/apps/cloud_federation_api/lib/Controller/RequestHandlerController.php @@ -4,6 +4,7 @@ * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -51,52 +52,19 @@ use Psr\Log\LoggerInterface; * @package OCA\CloudFederationAPI\Controller */ class RequestHandlerController extends Controller { - - /** @var LoggerInterface */ - private $logger; - - /** @var IUserManager */ - private $userManager; - - /** @var IGroupManager */ - private $groupManager; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var ICloudFederationProviderManager */ - private $cloudFederationProviderManager; - - /** @var Config */ - private $config; - - /** @var ICloudFederationFactory */ - private $factory; - - /** @var ICloudIdManager */ - private $cloudIdManager; - - public function __construct($appName, - IRequest $request, - LoggerInterface $logger, - IUserManager $userManager, - IGroupManager $groupManager, - IURLGenerator $urlGenerator, - ICloudFederationProviderManager $cloudFederationProviderManager, - Config $config, - ICloudFederationFactory $factory, - ICloudIdManager $cloudIdManager + public function __construct( + string $appName, + IRequest $request, + private LoggerInterface $logger, + private IUserManager $userManager, + private IGroupManager $groupManager, + private IURLGenerator $urlGenerator, + private ICloudFederationProviderManager $cloudFederationProviderManager, + private Config $config, + private ICloudFederationFactory $factory, + private ICloudIdManager $cloudIdManager ) { parent::__construct($appName, $request); - - $this->logger = $logger; - $this->userManager = $userManager; - $this->groupManager = $groupManager; - $this->urlGenerator = $urlGenerator; - $this->cloudFederationProviderManager = $cloudFederationProviderManager; - $this->config = $config; - $this->factory = $factory; - $this->cloudIdManager = $cloudIdManager; } /** @@ -122,7 +90,6 @@ class RequestHandlerController extends Controller { * Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares */ public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) { - // check if all required parameters are set if ($shareWith === null || $name === null || @@ -281,7 +248,7 @@ class RequestHandlerController extends Controller { ); } - return new JSONResponse($result,Http::STATUS_CREATED); + return new JSONResponse($result, Http::STATUS_CREATED); } /** diff --git a/apps/cloud_federation_api/openapi.json b/apps/cloud_federation_api/openapi.json index f017b864a27..e54324c9635 100644 --- a/apps/cloud_federation_api/openapi.json +++ b/apps/cloud_federation_api/openapi.json @@ -76,13 +76,8 @@ }, "protocols": { "type": "object", - "required": [ - "webdav" - ], - "properties": { - "webdav": { - "type": "string" - } + "additionalProperties": { + "type": "string" } } } diff --git a/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php b/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php index 687dcd25f8b..75606960e4b 100644 --- a/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php +++ b/apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php @@ -6,6 +6,7 @@ declare(strict_types=1); * @copyright 2018, Roeland Jago Douma <roeland@famdouma.nl> * * @author Christoph Wurst <christoph@winzerhof-wurst.at> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version @@ -29,21 +30,21 @@ namespace OCA\Files_Sharing\BackgroundJob; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; use OCP\IDBConnection; +use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\IOCMDiscoveryService; use OCP\OCS\IDiscoveryService; +use Psr\Log\LoggerInterface; class FederatedSharesDiscoverJob extends TimedJob { - /** @var IDBConnection */ - private $connection; - /** @var IDiscoveryService */ - private $discoveryService; - - public function __construct(ITimeFactory $time, - IDBConnection $connection, - IDiscoveryService $discoveryService) { - parent::__construct($time); - $this->connection = $connection; - $this->discoveryService = $discoveryService; + public function __construct( + ITimeFactory $time, + private IDBConnection $connection, + private IDiscoveryService $discoveryService, + private IOCMDiscoveryService $ocmDiscoveryService, + private LoggerInterface $logger, + ) { + parent::__construct($time); $this->setInterval(86400); } @@ -56,6 +57,11 @@ class FederatedSharesDiscoverJob extends TimedJob { $result = $qb->execute(); while ($row = $result->fetch()) { $this->discoveryService->discover($row['remote'], 'FEDERATED_SHARING', true); + try { + $this->ocmDiscoveryService->discover($row['remote'], true); + } catch (OCMProviderException $e) { + $this->logger->info('exception while running files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob', ['exception' => $e]); + } } $result->closeCursor(); } diff --git a/apps/files_sharing/lib/Controller/ExternalSharesController.php b/apps/files_sharing/lib/Controller/ExternalSharesController.php index 9fab8d4e1a0..4ddfa7e2e63 100644 --- a/apps/files_sharing/lib/Controller/ExternalSharesController.php +++ b/apps/files_sharing/lib/Controller/ExternalSharesController.php @@ -134,14 +134,14 @@ class ExternalSharesController extends Controller { } if ( - $this->testUrl('https://' . $remote . '/ocs-provider/') || - $this->testUrl('https://' . $remote . '/ocs-provider/index.php') || + $this->testUrl('https://' . $remote . '/ocm-provider/') || + $this->testUrl('https://' . $remote . '/ocm-provider/index.php') || $this->testUrl('https://' . $remote . '/status.php', true) ) { return new DataResponse('https'); } elseif ( - $this->testUrl('http://' . $remote . '/ocs-provider/') || - $this->testUrl('http://' . $remote . '/ocs-provider/index.php') || + $this->testUrl('http://' . $remote . '/ocm-provider/') || + $this->testUrl('http://' . $remote . '/ocm-provider/index.php') || $this->testUrl('http://' . $remote . '/status.php', true) ) { return new DataResponse('http'); diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php index f33334ca346..24cfc07e6d1 100644 --- a/apps/files_sharing/lib/External/Storage.php +++ b/apps/files_sharing/lib/External/Storage.php @@ -1,4 +1,7 @@ <?php + +declare(strict_types=1); + /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @@ -8,6 +11,7 @@ * @author Daniel Kesselberg <mail@danielkesselberg.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> @@ -36,8 +40,8 @@ use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use OC\Files\Storage\DAV; use OC\ForbiddenException; -use OCA\Files_Sharing\ISharedStorage; use OCA\Files_Sharing\External\Manager as ExternalShareManager; +use OCA\Files_Sharing\ISharedStorage; use OCP\AppFramework\Http; use OCP\Constants; use OCP\Federation\ICloudId; @@ -46,25 +50,23 @@ use OCP\Files\Storage\IDisableEncryptionStorage; use OCP\Files\Storage\IReliableEtagStorage; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; -use OCP\Http\Client\LocalServerException; use OCP\Http\Client\IClientService; +use OCP\Http\Client\LocalServerException; +use OCP\ICacheFactory; +use OCP\OCM\Exceptions\OCMArgumentException; +use OCP\OCM\Exceptions\OCMProviderException; +use OCP\OCM\IOCMDiscoveryService; +use OCP\Server; +use Psr\Log\LoggerInterface; class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, IReliableEtagStorage { - /** @var ICloudId */ - private $cloudId; - /** @var string */ - private $mountPoint; - /** @var string */ - private $token; - /** @var \OCP\ICacheFactory */ - private $memcacheFactory; - /** @var \OCP\Http\Client\IClientService */ - private $httpClient; - /** @var bool */ - private $updateChecked = false; - - /** @var ExternalShareManager */ - private $manager; + private ICloudId $cloudId; + private string $mountPoint; + private string $token; + private ICacheFactory $memcacheFactory; + private IClientService $httpClient; + private bool $updateChecked = false; + private ExternalShareManager $manager; /** * @param array{HttpClientService: IClientService, manager: ExternalShareManager, cloudId: ICloudId, mountpoint: string, token: string, password: ?string}|array $options @@ -72,32 +74,45 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, public function __construct($options) { $this->memcacheFactory = \OC::$server->getMemCacheFactory(); $this->httpClient = $options['HttpClientService']; - $this->manager = $options['manager']; $this->cloudId = $options['cloudId']; - $discoveryService = \OC::$server->query(\OCP\OCS\IDiscoveryService::class); + $this->logger = Server::get(LoggerInterface::class); + $discoveryService = Server::get(IOCMDiscoveryService::class); - [$protocol, $remote] = explode('://', $this->cloudId->getRemote()); - if (strpos($remote, '/')) { - [$host, $root] = explode('/', $remote, 2); - } else { - $host = $remote; - $root = ''; + // use default path to webdav if not found on discovery + try { + $ocmProvider = $discoveryService->discover($this->cloudId->getRemote()); + $webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav'); + $remote = $ocmProvider->getEndPoint(); + } catch (OCMProviderException|OCMArgumentException $e) { + $this->logger->notice('exception while retrieving webdav endpoint', ['exception' => $e]); + $webDavEndpoint = '/public.php/webdav'; + $remote = $this->cloudId->getRemote(); + } + + $host = parse_url($remote, PHP_URL_HOST); + $port = parse_url($remote, PHP_URL_PORT); + $host .= (null === $port) ? '' : ':' . $port; // we add port if available + + // in case remote NC is on a sub folder and using deprecated ocm provider + $tmpPath = rtrim(parse_url($this->cloudId->getRemote(), PHP_URL_PATH) ?? '', '/'); + if (!str_starts_with($webDavEndpoint, $tmpPath)) { + $webDavEndpoint = $tmpPath . $webDavEndpoint; } - $secure = $protocol === 'https'; - $federatedSharingEndpoints = $discoveryService->discover($this->cloudId->getRemote(), 'FEDERATED_SHARING'); - $webDavEndpoint = isset($federatedSharingEndpoints['webdav']) ? $federatedSharingEndpoints['webdav'] : '/public.php/webdav'; - $root = rtrim($root, '/') . $webDavEndpoint; + $this->mountPoint = $options['mountpoint']; $this->token = $options['token']; - parent::__construct([ - 'secure' => $secure, - 'host' => $host, - 'root' => $root, - 'user' => $options['token'], - 'password' => (string)$options['password'] - ]); + parent::__construct( + [ + 'secure' => ((parse_url($remote, PHP_URL_SCHEME) ?? 'https') === 'https'), + 'host' => $host, + 'root' => $webDavEndpoint, + 'user' => $options['token'], + 'authType' => \Sabre\DAV\Client::AUTH_BASIC, + 'password' => (string)$options['password'] + ] + ); } public function getWatcher($path = '', $storage = null) { @@ -255,9 +270,9 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, */ protected function testRemote(): bool { try { - return $this->testRemoteUrl($this->getRemote() . '/ocs-provider/index.php') - || $this->testRemoteUrl($this->getRemote() . '/ocs-provider/') - || $this->testRemoteUrl($this->getRemote() . '/status.php'); + return $this->testRemoteUrl($this->getRemote() . '/ocm-provider/index.php') + || $this->testRemoteUrl($this->getRemote() . '/ocm-provider/') + || $this->testRemoteUrl($this->getRemote() . '/status.php'); } catch (\Exception $e) { return false; } diff --git a/apps/files_sharing/tests/ExternalStorageTest.php b/apps/files_sharing/tests/ExternalStorageTest.php index 7709abbf6eb..d180b06d641 100644 --- a/apps/files_sharing/tests/ExternalStorageTest.php +++ b/apps/files_sharing/tests/ExternalStorageTest.php @@ -28,6 +28,7 @@ namespace OCA\Files_Sharing\Tests; use OC\Federation\CloudId; +use OCA\Files_Sharing\External\Manager as ExternalShareManager; use OCP\Http\Client\IClient; use OCP\Http\Client\IClientService; use OCP\Http\Client\IResponse; @@ -75,6 +76,7 @@ class ExternalStorageTest extends \Test\TestCase { private function getTestStorage($uri) { $certificateManager = \OC::$server->getCertificateManager(); $httpClientService = $this->createMock(IClientService::class); + $manager = $this->createMock(ExternalShareManager::class); $client = $this->createMock(IClient::class); $response = $this->createMock(IResponse::class); $client @@ -98,7 +100,7 @@ class ExternalStorageTest extends \Test\TestCase { 'mountpoint' => 'remoteshare', 'token' => 'abcdef', 'password' => '', - 'manager' => null, + 'manager' => $manager, 'certificateManager' => $certificateManager, 'HttpClientService' => $httpClientService, ] diff --git a/build/files-checker.php b/build/files-checker.php index c06e5ea81dc..d8bd3a13803 100644 --- a/build/files-checker.php +++ b/build/files-checker.php @@ -77,7 +77,6 @@ $expectedFiles = [ 'jest.config.js', 'lib', 'occ', - 'ocm-provider', 'ocs', 'ocs-provider', 'package-lock.json', diff --git a/core/Controller/OCMController.php b/core/Controller/OCMController.php new file mode 100644 index 00000000000..de24cc75191 --- /dev/null +++ b/core/Controller/OCMController.php @@ -0,0 +1,97 @@ +<?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 OC\Core\Controller; + +use Exception; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\DataResponse; +use OCP\Capabilities\ICapability; +use OCP\IConfig; +use OCP\IRequest; +use OCP\Server; +use Psr\Container\ContainerExceptionInterface; +use Psr\Log\LoggerInterface; + +/** + * Controller about the endpoint /ocm-provider/ + * + * @since 28.0.0 + */ +class OCMController extends Controller { + public function __construct( + IRequest $request, + private IConfig $config, + private LoggerInterface $logger + ) { + parent::__construct('core', $request); + } + + /** + * generate a OCMProvider with local data and send it as DataResponse. + * This replaces the old PHP file ocm-provider/index.php + * + * @PublicPage + * @NoCSRFRequired + * + * @return DataResponse + * + * 200: OCM Provider details returned + * 500: OCM not supported + */ + public function discovery(): DataResponse { + try { + $cap = Server::get( + $this->config->getAppValue( + 'core', + 'ocm_providers', + '\OCA\CloudFederationAPI\Capabilities' + ) + ); + + if (!($cap instanceof ICapability)) { + throw new Exception('loaded class does not implements OCP\Capabilities\ICapability'); + } + + return new DataResponse( + $cap->getCapabilities()['ocm'] ?? ['enabled' => false], + Http::STATUS_OK, + [ + 'X-NEXTCLOUD-OCM-PROVIDERS' => true, + 'Content-Type' => 'application/json' + ] + ); + } catch (ContainerExceptionInterface|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/core/openapi.json b/core/openapi.json index 06e0047a190..fdc4bd3e6b8 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -1275,6 +1275,116 @@ } } }, + "/index.php/ocm-provider": { + "get": { + "operationId": "ocm-discovery", + "summary": "generate a OCMProvider with local data and send it as DataResponse. This replaces the old PHP file ocm-provider/index.php", + "tags": [ + "ocm" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "responses": { + "200": { + "description": "OCM Provider details returned", + "headers": { + "X-NEXTCLOUD-OCM-PROVIDERS": { + "schema": { + "type": "boolean" + } + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "enabled", + "apiVersion", + "endPoint", + "resourceTypes" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "apiVersion": { + "type": "string" + }, + "endPoint": { + "type": "string" + }, + "resourceTypes": { + "type": "object", + "required": [ + null + ], + "properties": { + "": { + "type": "object", + "required": [ + "name", + "shareTypes", + "protocols" + ], + "properties": { + "name": { + "type": "string" + }, + "shareTypes": { + "type": "array", + "items": { + "type": "string" + } + }, + "protocols": { + "type": "object", + "required": [ + "webdav" + ], + "properties": { + "webdav": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "500": { + "description": "OCM not supported", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/cloud/capabilities": { "get": { "operationId": "ocs-get-capabilities", @@ -4329,6 +4439,10 @@ { "name": "guest_avatar", "description": "This controller handles guest avatar requests." + }, + { + "name": "ocm", + "description": "Controller about the endpoint /ocm-provider/" } ] }
\ No newline at end of file diff --git a/core/routes.php b/core/routes.php index ad8638e0b1e..fcb5a15cf01 100644 --- a/core/routes.php +++ b/core/routes.php @@ -13,6 +13,7 @@ declare(strict_types=1); * @author John Molakvoæ <skjnldsv@protonmail.com> * @author Julius Härtl <jus@bitgrid.net> * @author Lukas Reschke <lukas@statuscode.ch> + * @author Maxence Lange <maxence@artificial-owl.com> * @author Michael Weimann <mail@michael-weimann.eu> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> @@ -103,6 +104,9 @@ $application->registerRoutes($this, [ // Well known requests https://tools.ietf.org/html/rfc5785 ['name' => 'WellKnown#handle', 'url' => '.well-known/{service}'], + // OCM Provider requests https://github.com/cs3org/OCM-API + ['name' => 'OCM#discovery', 'url' => '/ocm-provider/'], + // Unsupported browser ['name' => 'UnsupportedBrowser#index', 'url' => 'unsupported'], ], diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 85f2324ddf5..c97f697dd0b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -520,6 +520,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', @@ -1048,6 +1053,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', @@ -1438,6 +1444,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 2b430ca0fcc..47612fc774c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -553,6 +553,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', @@ -1081,6 +1086,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', @@ -1471,6 +1477,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..44f4fbebc10 --- /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 = []; + 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 2d2f2cd9a9b..5bcb9ada9f5 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -125,6 +125,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; @@ -235,6 +236,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; @@ -1371,6 +1373,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( @@ -1386,9 +1389,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; +} diff --git a/ocm-provider/index.php b/ocm-provider/index.php deleted file mode 100644 index 1b90fd53665..00000000000 --- a/ocm-provider/index.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2018 Bjoern Schiessle <bjoern@schiessle.org> - * - * @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/>. - * - */ - - -require_once __DIR__ . '/../lib/base.php'; - -header('Content-Type: application/json'); - -$server = \OC::$server; - -$isEnabled = $server->getAppManager()->isEnabledForUser('cloud_federation_api'); - -if ($isEnabled) { - // Make sure the routes are loaded - \OC_App::loadApp('cloud_federation_api'); - $capabilities = new OCA\CloudFederationAPI\Capabilities($server->getURLGenerator()); - header('Content-Type: application/json'); - echo json_encode($capabilities->getCapabilities()['ocm']); -} else { - header($_SERVER["SERVER_PROTOCOL"]." 501 Not Implemented", true, 501); - exit("501 Not Implemented"); -} diff --git a/psalm.xml b/psalm.xml index 573879adaa8..b18ebc93cdd 100644 --- a/psalm.xml +++ b/psalm.xml @@ -43,7 +43,6 @@ <directory name="apps/workflowengine"/> <directory name="core"/> <directory name="lib"/> - <directory name="ocm-provider"/> <directory name="ocs"/> <directory name="ocs-provider"/> <file name="index.php"/> |