diff --git a/apps/cloud_federation_api/lib/Capabilities.php b/apps/cloud_federation_api/lib/Capabilities.php
index e17374c08f739..9c94aa68a8cec 100644
--- a/apps/cloud_federation_api/lib/Capabilities.php
+++ b/apps/cloud_federation_api/lib/Capabilities.php
@@ -31,7 +31,7 @@
use OC\OCM\Model\OCMResource;
use OCP\Capabilities\ICapability;
use OCP\IURLGenerator;
-use OCP\OCM\IOCMDiscoveryService;
+use OCP\OCM\Exceptions\OCMArgumentException;
class Capabilities implements ICapability {
@@ -39,27 +39,27 @@ class Capabilities implements ICapability {
public function __construct(
private IURLGenerator $urlGenerator,
- private IOCMDiscoveryService $discoveryService
) {
}
/**
* Function an app uses to return the capabilities
*
- * @return array{
- * ocm: array{
+ * @return array {
+ * ocm: array {
* enabled: bool,
* apiVersion: string,
* endPoint: string,
- * resourceTypes: array{
+ * resourceTypes: array {
* name: string,
* shareTypes: string[],
- * protocols: array{
+ * protocols: array {
* webdav: string,
- * },
- * }[],
- * },
+ * },
+ * }[],
+ * },
* }
+ * @throws OCMArgumentException
*/
public function getCapabilities() {
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
@@ -67,15 +67,21 @@ public function getCapabilities() {
$provider = new OCMProvider();
$provider->setEnabled(true);
$provider->setApiVersion(self::API_VERSION);
- $provider->setEndPoint(substr($url, 0, strrpos($url, '/')));
+
+ $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/']);
+ ->setShareTypes(['user', 'group'])
+ ->setProtocols(['webdav' => '/public.php/webdav/']);
$provider->setResourceTypes([$resource]);
- return ['ocm' => $provider];
+ return ['ocm' => $provider->jsonSerialize()];
}
}
diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php
index b750e2b76cffc..735f74b0a6067 100644
--- a/apps/files_sharing/lib/External/Storage.php
+++ b/apps/files_sharing/lib/External/Storage.php
@@ -82,7 +82,7 @@ public function __construct($options) {
// use default path to webdav if not found on discovery
try {
$ocmProvider = $discoveryService->discover($this->cloudId->getRemote());
- $webDavEndpoint = $ocmProvider->extractProtocolUrl('file', 'webdav');
+ $webDavEndpoint = $ocmProvider->extractProtocolEntry('file', 'webdav');
$secure = (parse_url($ocmProvider->getEndPoint(), PHP_URL_SCHEME) === 'https');
$host = parse_url($ocmProvider->getEndPoint(), PHP_URL_HOST);
} catch (OCMProviderException|OCMArgumentException $e) {
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 3d56f710237c1..7be4bc475623b 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -43,6 +43,11 @@
IEventListener
+
+
+ array
+
+
IEventListener
diff --git a/core/Controller/OCMController.php b/core/Controller/OCMController.php
index fcfaadc87ab1c..04983d21c6817 100644
--- a/core/Controller/OCMController.php
+++ b/core/Controller/OCMController.php
@@ -34,8 +34,6 @@
use OCP\IConfig;
use OCP\IRequest;
use OCP\Server;
-use Psr\Container\ContainerExceptionInterface;
-use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
/**
@@ -44,7 +42,6 @@
* @since 28.0.0
*/
class OCMController extends Controller {
-
public function __construct(
IRequest $request,
private IConfig $config,
@@ -85,7 +82,7 @@ public function discovery(): Response {
return new DataResponse(
['message' => '/ocm-provider/ not supported'],
- Http::STATUS_NOT_FOUND
+ Http::STATUS_INTERNAL_SERVER_ERROR
);
}
}
diff --git a/lib/private/Federation/CloudFederationProviderManager.php b/lib/private/Federation/CloudFederationProviderManager.php
index db6c67fb2f0ad..ea2f0dd7575f0 100644
--- a/lib/private/Federation/CloudFederationProviderManager.php
+++ b/lib/private/Federation/CloudFederationProviderManager.php
@@ -49,14 +49,8 @@
* @package OC\Federation
*/
class CloudFederationProviderManager implements ICloudFederationProviderManager {
-
/** @var array list of available cloud federation providers */
private array $cloudFederationProvider = [];
- private array $ocmEndPoints = [];
- private array $supportedAPIVersion = [
- '1.0-proposal1',
- '1.0',
- ];
public function __construct(
private IConfig $config,
diff --git a/lib/private/OCM/Model/OCMProvider.php b/lib/private/OCM/Model/OCMProvider.php
index 50854b1b6e241..8f78ebf8cc591 100644
--- a/lib/private/OCM/Model/OCMProvider.php
+++ b/lib/private/OCM/Model/OCMProvider.php
@@ -28,6 +28,7 @@
use JsonSerializable;
use OCP\OCM\Exceptions\OCMArgumentException;
+use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMProvider;
/**
@@ -87,9 +88,7 @@ public function getApiVersion(): string {
* @return OCMProvider
*/
public function setEndPoint(string $endPoint): self {
- if ($this->isSafeUrl(parse_url($endPoint, PHP_URL_PATH) ?? '')) {
- $this->endPoint = $endPoint;
- }
+ $this->endPoint = $endPoint;
return $this;
}
@@ -106,7 +105,7 @@ public function getEndPoint(): string {
*
* @return $this
*/
- public function addResource(OCMResource $resource): self {
+ public function addResourceType(OCMResource $resource): self {
$this->resourceTypes[] = $resource;
return $this;
@@ -130,44 +129,6 @@ public function getResourceTypes(): array {
return $this->resourceTypes;
}
- /**
- * @return bool
- */
- public function looksValid(): bool {
- if ($this->url !== ''
- && parse_url($this->url, PHP_URL_HOST) !==
- parse_url($this->getEndPoint(), PHP_URL_HOST)) {
- return false;
- }
-
- return ($this->getApiVersion() !== '' && $this->getEndPoint() !== '');
- }
-
- /**
- * @param string $url
- *
- * @return bool
- */
- protected function isSafeUrl(string $url): bool {
- return (bool)preg_match('/^[\/\.\-A-Za-z0-9]+$/', $url);
- }
-
- /**
- * @param string $resourceName
- * @param string $protocol
- *
- * @return string
- * @throws OCMArgumentException
- */
- public function extractProtocolUrl(string $resourceName, string $protocol): string {
- $url = $this->extractProtocolEntry($resourceName, $protocol);
- if (!$this->isSafeUrl($url)) {
- throw new OCMArgumentException('url does not looks safe');
- }
-
- return $url;
- }
-
/**
* @param string $resourceName
* @param string $protocol
@@ -193,18 +154,15 @@ public function extractProtocolEntry(string $resourceName, string $protocol): st
/**
* import data from an array
*
- * @param array|null $data
+ * @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 {
- if (is_null($data)) {
- return $this;
- }
-
+ public function import(array $data): self {
$this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
- ->setApiVersion((string)$data['apiVersion'] ?? '')
+ ->setApiVersion((string)($data['apiVersion'] ?? ''))
->setEndPoint($data['endPoint'] ?? '');
$resources = [];
@@ -214,9 +172,30 @@ public function import(?array $data): self {
}
$this->setResourceTypes($resources);
+ if (!$this->looksValid()) {
+ throw new OCMProviderException('remote provider does not look valid');
+ }
+
return $this;
}
+
+ /**
+ * @return bool
+ */
+ private function looksValid(): bool {
+ if ($this->url !== ''
+ && parse_url($this->url, PHP_URL_HOST) !==
+ parse_url($this->getEndPoint(), PHP_URL_HOST)) {
+ return false;
+ }
+
+ return ($this->getApiVersion() !== '' && $this->getEndPoint() !== '');
+ }
+
+
+
+
public function jsonSerialize(): array {
return [
'enabled' => $this->isEnabled(),
diff --git a/lib/private/OCM/Model/OCMResource.php b/lib/private/OCM/Model/OCMResource.php
index 99200403d14c1..77e997924a5c6 100644
--- a/lib/private/OCM/Model/OCMResource.php
+++ b/lib/private/OCM/Model/OCMResource.php
@@ -34,7 +34,9 @@
*/
class OCMResource implements IOCMResource, JsonSerializable {
private string $name = '';
+ /** @var string[] */
private array $shareTypes = [];
+ /** @var array */
private array $protocols = [];
/**
@@ -56,7 +58,7 @@ public function getName(): string {
}
/**
- * @param array $shareTypes
+ * @param string[] $shareTypes
*
* @return OCMResource
*/
@@ -67,14 +69,14 @@ public function setShareTypes(array $shareTypes): self {
}
/**
- * @return array
+ * @return string[]
*/
public function getShareTypes(): array {
return $this->shareTypes;
}
/**
- * @param array $protocols
+ * @param array $protocols
*
* @return $this
*/
@@ -85,7 +87,7 @@ public function setProtocols(array $protocols): self {
}
/**
- * @return array
+ * @return array
*/
public function getProtocols(): array {
return $this->protocols;
@@ -100,7 +102,7 @@ public function getProtocols(): array {
* @return self
*/
public function import(array $data): self {
- return $this->setName((string)$data['name'] ?? '')
+ return $this->setName((string)($data['name'] ?? ''))
->setShareTypes($data['shareTypes'] ?? [])
->setProtocols($data['protocols'] ?? []);
}
diff --git a/lib/private/OCM/OCMDiscoveryService.php b/lib/private/OCM/OCMDiscoveryService.php
index 0fa760bf4a562..92f394cf4aed2 100644
--- a/lib/private/OCM/OCMDiscoveryService.php
+++ b/lib/private/OCM/OCMDiscoveryService.php
@@ -26,12 +26,14 @@
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\IL10N;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMDiscoveryService;
use OCP\OCM\IOCMProvider;
@@ -42,11 +44,17 @@
*/
class OCMDiscoveryService implements IOCMDiscoveryService {
private ICache $cache;
+ private array $supportedAPIVersion =
+ [
+ '1.0-proposal1',
+ '1.0',
+ ];
public function __construct(
ICacheFactory $cacheFactory,
private IClientService $clientService,
private IConfig $config,
+ private IL10N $l10n,
private LoggerInterface $logger
) {
$this->cache = $cacheFactory->createDistributed('ocm-discovery');
@@ -65,9 +73,13 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
$provider = new OCMProvider($remote);
if (!$skipCache) {
- $provider->import(json_decode($this->cache->get($remote) ?? '', true));
- if ($provider->looksValid()) {
- return $provider; // if cache looks valid, we use it
+ try {
+ $provider->import(json_decode($this->cache->get($remote) ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
+ if (in_array($provider->getApiVersion(), $this->supportedAPIVersion)) {
+ return $provider; // if cache looks valid, we use it
+ }
+ } catch (JsonException|OCMProviderException $e) {
+ // we ignore cache on issues
}
}
@@ -85,18 +97,21 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
if ($response->getStatusCode() === Http::STATUS_OK) {
$body = $response->getBody();
// update provider with data returned by the request
- $provider->import(json_decode($body, true));
+ $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 (!$provider->looksValid()) {
- throw new OCMProviderException('remote provider does not look valid');
+ if (!in_array($provider->getApiVersion(), $this->supportedAPIVersion)) {
+ throw new OCMProviderException($this->l10n->t('unsupported version (%1)', [$provider->getApiVersion()]));
}
return $provider;
diff --git a/lib/public/OCM/Exceptions/OCMArgumentException.php b/lib/public/OCM/Exceptions/OCMArgumentException.php
index 8a3f1c03fee82..e3abd7bf26bab 100644
--- a/lib/public/OCM/Exceptions/OCMArgumentException.php
+++ b/lib/public/OCM/Exceptions/OCMArgumentException.php
@@ -27,5 +27,8 @@
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
index 3257177d0049e..32dab10dc68d6 100644
--- a/lib/public/OCM/Exceptions/OCMProviderException.php
+++ b/lib/public/OCM/Exceptions/OCMProviderException.php
@@ -27,5 +27,8 @@
use Exception;
+/**
+ * @since 28.0.0
+ */
class OCMProviderException extends Exception {
}
diff --git a/lib/public/OCM/IOCMProvider.php b/lib/public/OCM/IOCMProvider.php
index e289fef144b22..f99ccf1cd238e 100644
--- a/lib/public/OCM/IOCMProvider.php
+++ b/lib/public/OCM/IOCMProvider.php
@@ -28,6 +28,7 @@
use OC\OCM\Model\OCMResource;
use OCP\OCM\Exceptions\OCMArgumentException;
+use OCP\OCM\Exceptions\OCMProviderException;
/**
* Model based on the Open Cloud Mesh Discovery API
@@ -35,7 +36,6 @@
* @since 28.0.0
*/
interface IOCMProvider {
-
/**
* enable OCM
*
@@ -98,7 +98,7 @@ public function getEndPoint(): string;
* @return self
* @since 28.0.0
*/
- public function addResource(OCMResource $resource): self;
+ public function addResourceType(OCMResource $resource): self;
/**
* set resources
@@ -118,26 +118,6 @@ public function setResourceTypes(array $resourceTypes): self;
*/
public function getResourceTypes(): array;
- /**
- * returns if object have its required parameters well set
- *
- * @return bool
- * @since 28.0.0
- */
- public function looksValid(): bool;
-
- /**
- * extract a safe url 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 extractProtocolUrl(string $resourceName, string $protocol): string;
-
/**
* extract a specific string value from the listing of protocols, based on resource-name and protocol-name
*
@@ -156,6 +136,7 @@ public function extractProtocolEntry(string $resourceName, string $protocol): st
* @param 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
index 2ed46c2b9c705..381af61cecc4c 100644
--- a/lib/public/OCM/IOCMResource.php
+++ b/lib/public/OCM/IOCMResource.php
@@ -33,7 +33,6 @@
* @since 28.0.0
*/
interface IOCMResource {
-
/**
* set name of the resource
*
@@ -48,13 +47,14 @@ 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 array $shareTypes
+ * @param string[] $shareTypes
*
* @return self
* @since 28.0.0
@@ -64,7 +64,7 @@ public function setShareTypes(array $shareTypes): self;
/**
* get share types
*
- * @return array
+ * @return string[]
* @since 28.0.0
*/
public function getShareTypes(): array;
@@ -72,7 +72,7 @@ public function getShareTypes(): array;
/**
* set available protocols
*
- * @param array $protocols
+ * @param array $protocols
*
* @return self
* @since 28.0.0
@@ -82,7 +82,7 @@ public function setProtocols(array $protocols): self;
/**
* get configured protocols
*
- * @return array
+ * @return array
* @since 28.0.0
*/
public function getProtocols(): array;