summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaxence Lange <maxence@artificial-owl.com>2023-09-23 14:08:05 -0100
committerGitHub <noreply@github.com>2023-09-23 14:08:05 -0100
commitb06bff7922846f20ca414f9528d11a47a43fde48 (patch)
tree5da3977f37f26af41a08b8567f3628f82232907f
parent6d8cbd5ede73c4679b384b9e45da80f7d41aa865 (diff)
parent0fac750bcd5eccc65f2861a3bb7633912fb30039 (diff)
downloadnextcloud-server-b06bff7922846f20ca414f9528d11a47a43fde48.tar.gz
nextcloud-server-b06bff7922846f20ca414f9528d11a47a43fde48.zip
Merge pull request #40586 from nextcloud/backport/39574/39574-stable27
[stable27] ocm services
-rw-r--r--apps/cloud_federation_api/appinfo/routes.php15
-rw-r--r--apps/cloud_federation_api/lib/Capabilities.php65
-rw-r--r--apps/cloud_federation_api/lib/Controller/RequestHandlerController.php59
-rw-r--r--apps/cloud_federation_api/openapi.json9
-rw-r--r--apps/files_sharing/lib/BackgroundJob/FederatedSharesDiscoverJob.php28
-rw-r--r--apps/files_sharing/lib/Controller/ExternalSharesController.php8
-rw-r--r--apps/files_sharing/lib/External/Storage.php93
-rw-r--r--apps/files_sharing/tests/ExternalStorageTest.php4
-rw-r--r--build/files-checker.php1
-rw-r--r--core/Controller/OCMController.php97
-rw-r--r--core/openapi.json114
-rw-r--r--core/routes.php4
-rw-r--r--lib/composer/composer/autoload_classmap.php9
-rw-r--r--lib/composer/composer/autoload_static.php9
-rw-r--r--lib/private/Federation/CloudFederationProviderManager.php100
-rw-r--r--lib/private/OCM/Model/OCMProvider.php211
-rw-r--r--lib/private/OCM/Model/OCMResource.php125
-rw-r--r--lib/private/OCM/OCMDiscoveryService.php138
-rw-r--r--lib/private/Server.php5
-rw-r--r--lib/public/OCM/Exceptions/OCMArgumentException.php34
-rw-r--r--lib/public/OCM/Exceptions/OCMProviderException.php34
-rw-r--r--lib/public/OCM/IOCMDiscoveryService.php48
-rw-r--r--lib/public/OCM/IOCMProvider.php143
-rw-r--r--lib/public/OCM/IOCMResource.php99
-rw-r--r--ocm-provider/index.php40
-rw-r--r--psalm.xml1
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"/>