]> source.dussan.org Git - nextcloud-server.git/commitdiff
ocm services 40586/head
authorMaxence Lange <maxence@artificial-owl.com>
Fri, 22 Sep 2023 18:25:33 +0000 (17:25 -0100)
committerMaxence Lange <maxence@artificial-owl.com>
Fri, 22 Sep 2023 18:26:02 +0000 (17:26 -0100)
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
26 files changed:
apps/cloud_federation_api/appinfo/routes.php
apps/cloud_federation_api/lib/Capabilities.php
apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
apps/cloud_federation_api/openapi.json
apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php
apps/files_sharing/lib/Controller/ExternalSharesController.php
apps/files_sharing/lib/External/Storage.php
apps/files_sharing/tests/ExternalStorageTest.php
build/files-checker.php
core/Controller/OCMController.php [new file with mode: 0644]
core/openapi.json
core/routes.php
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Federation/CloudFederationProviderManager.php
lib/private/OCM/Model/OCMProvider.php [new file with mode: 0644]
lib/private/OCM/Model/OCMResource.php [new file with mode: 0644]
lib/private/OCM/OCMDiscoveryService.php [new file with mode: 0644]
lib/private/Server.php
lib/public/OCM/Exceptions/OCMArgumentException.php [new file with mode: 0644]
lib/public/OCM/Exceptions/OCMProviderException.php [new file with mode: 0644]
lib/public/OCM/IOCMDiscoveryService.php [new file with mode: 0644]
lib/public/OCM/IOCMProvider.php [new file with mode: 0644]
lib/public/OCM/IOCMResource.php [new file with mode: 0644]
ocm-provider/index.php [deleted file]
psalm.xml

index d70b06f821c48e5107a30ded467b3597b7e056c5..966ff2ce3a17986b623a8316964433b07b7d9954 100644 (file)
@@ -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',
+//             ]
        ],
 ];
index 6164c0e0dba6cfd6a574ae8c314eeee23e166b3b..60d6186b840930c2adbc47c2605c262af141772a 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+
+declare(strict_types=1);
+
 /**
  * @copyright Copyright (c) 2017 Bjoern Schiessle <bjoern@schiessle.org>
  *
  * 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()];
        }
 }
index ef77f2fa317de53c16218737eca706637ed0d72b..fd55746a83c65acf682d8106dddf7d22df87acb7 100644 (file)
@@ -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);
        }
 
        /**
index f017b864a27279b7822ce9b529c01245439ac479..e54324c96359df134773d37aaf84f2acdbf0dadb 100644 (file)
                                         },
                                         "protocols": {
                                             "type": "object",
-                                            "required": [
-                                                "webdav"
-                                            ],
-                                            "properties": {
-                                                "webdav": {
-                                                    "type": "string"
-                                                }
+                                            "additionalProperties": {
+                                                "type": "string"
                                             }
                                         }
                                     }
index 687dcd25f8b4ee35a3013f5d81ae4b8ef7aa97ac..75606960e4b429283f71e3dbaa85659db1f1e020 100644 (file)
@@ -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();
        }
index 9fab8d4e1a0517f6c12e8fb6dcd79db5f03efc1e..4ddfa7e2e638cc1c8f7983fa656700b702dd31ff 100644 (file)
@@ -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');
index f33334ca3466dea8f79918d6daa0235a7b15b079..24cfc07e6d1a08f29bd700b0a5c79d3207b10268 100644 (file)
@@ -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;
                }
index 7709abbf6eb53697d9f61a83e326183224f02e35..d180b06d641f09ace327f42f37bd4805fd6adfe1 100644 (file)
@@ -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,
                        ]
index c06e5ea81dc3965057e231539437c504bdec2b93..d8bd3a13803c000e611e15c88fb0721a7f2dce56 100644 (file)
@@ -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 (file)
index 0000000..de24cc7
--- /dev/null
@@ -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
+                       );
+               }
+       }
+}
index 06e0047a1900634e265822ec16954ab1c210f03d..fdc4bd3e6b81df267452dd1cf185383ce5c7fcb3 100644 (file)
                 }
             }
         },
+        "/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",
         {
             "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
index ad8638e0b1e4ee6b55c80636f84770a39f84e722..fcb5a15cf0120d447ad311c5ee0d1c4f0ea83e65 100644 (file)
@@ -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'],
        ],
index 85f2324ddf53b4af6ed07894150cfc0f8d4624a3..c97f697dd0b05039ad48aab3d4733e7b2961433c 100644 (file)
@@ -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',
index 2b430ca0fccd96fce0b156b8b05a5be02f777581..47612fc774c6a7c8c1acb80020f91e56deac1996 100644 (file)
@@ -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',
index b11c4060ab4f2b29550e082313d86a8009c90065..ea2f0dd7575f0f459fd1362c2a11e135f5c119f8 100644 (file)
@@ -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 (file)
index 0000000..44f4fbe
--- /dev/null
@@ -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 (file)
index 0000000..12948aa
--- /dev/null
@@ -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 (file)
index 0000000..e3b1d35
--- /dev/null
@@ -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));
+       }
+}
index 2d2f2cd9a9bedbe01cc0c69c3a7553f1148b0295..5bcb9ada9f5b51ce98dda2b25596729f6755fa24 100644 (file)
@@ -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 (file)
index 0000000..e3abd7b
--- /dev/null
@@ -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 (file)
index 0000000..32dab10
--- /dev/null
@@ -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 (file)
index 0000000..2407e7b
--- /dev/null
@@ -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 (file)
index 0000000..f99ccf1
--- /dev/null
@@ -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 (file)
index 0000000..381af61
--- /dev/null
@@ -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 (file)
index 1b90fd5..0000000
+++ /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");
-}
index 573879adaa80a7ccb9ce4059d3d6487095bd9fdb..b18ebc93cdd66cb13cdafd7d24bb3d801ea6a8d6 100644 (file)
--- 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"/>