mirror of
https://github.com/nextcloud/server.git
synced 2026-02-20 00:12:30 -05:00
Merge pull request #56899 from nextcloud/feat/noid/ocm-capabilities
This commit is contained in:
commit
ae250777fd
26 changed files with 962 additions and 170 deletions
|
|
@ -25,6 +25,15 @@ return [
|
|||
'url' => '/invite-accepted',
|
||||
'verb' => 'POST',
|
||||
'root' => '/ocm',
|
||||
]
|
||||
],
|
||||
|
||||
// needs to be kept at the bottom of the list
|
||||
[
|
||||
'name' => 'OCMRequest#manageOCMRequests',
|
||||
'url' => '/{ocmPath}',
|
||||
'requirements' => ['ocmPath' => '.*'],
|
||||
'verb' => ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
'root' => '/ocm',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ return array(
|
|||
'OCA\\CloudFederationAPI\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\CloudFederationAPI\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
|
||||
'OCA\\CloudFederationAPI\\Config' => $baseDir . '/../lib/Config.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\OCMRequestController' => $baseDir . '/../lib/Controller/OCMRequestController.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => $baseDir . '/../lib/Controller/RequestHandlerController.php',
|
||||
'OCA\\CloudFederationAPI\\Db\\FederatedInvite' => $baseDir . '/../lib/Db/FederatedInvite.php',
|
||||
'OCA\\CloudFederationAPI\\Db\\FederatedInviteMapper' => $baseDir . '/../lib/Db/FederatedInviteMapper.php',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class ComposerStaticInitCloudFederationAPI
|
|||
'OCA\\CloudFederationAPI\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\CloudFederationAPI\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
|
||||
'OCA\\CloudFederationAPI\\Config' => __DIR__ . '/..' . '/../lib/Config.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\OCMRequestController' => __DIR__ . '/..' . '/../lib/Controller/OCMRequestController.php',
|
||||
'OCA\\CloudFederationAPI\\Controller\\RequestHandlerController' => __DIR__ . '/..' . '/../lib/Controller/RequestHandlerController.php',
|
||||
'OCA\\CloudFederationAPI\\Db\\FederatedInvite' => __DIR__ . '/..' . '/../lib/Db/FederatedInvite.php',
|
||||
'OCA\\CloudFederationAPI\\Db\\FederatedInviteMapper' => __DIR__ . '/..' . '/../lib/Db/FederatedInviteMapper.php',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\CloudFederationAPI\Controller;
|
||||
|
||||
use JsonException;
|
||||
use NCU\Security\Signature\Exceptions\IncomingRequestException;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\BruteForceProtection;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IRequest;
|
||||
use OCP\OCM\Events\OCMEndpointRequestEvent;
|
||||
use OCP\OCM\Exceptions\OCMArgumentException;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class OCMRequestController extends Controller {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private readonly IEventDispatcher $eventDispatcher,
|
||||
private readonly IOCMDiscoveryService $ocmDiscoveryService,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method will catch any request done to /ocm/[...] and will broadcast an event.
|
||||
* The first parameter of the remaining subpath (post-/ocm/) is defined as
|
||||
* capability and should be used by listeners to filter incoming requests.
|
||||
*
|
||||
* @see OCMEndpointRequestEvent
|
||||
* @see OCMEndpointRequestEvent::getArgs
|
||||
*
|
||||
* @param string $ocmPath
|
||||
* @return Response
|
||||
* @throws OCMArgumentException
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
#[BruteForceProtection(action: 'receiveOcmRequest')]
|
||||
public function manageOCMRequests(string $ocmPath): Response {
|
||||
if (!mb_check_encoding($ocmPath, 'UTF-8')) {
|
||||
throw new OCMArgumentException('path is not UTF-8');
|
||||
}
|
||||
|
||||
try {
|
||||
// if request is signed and well signed, no exceptions are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exceptions are thrown
|
||||
$signedRequest = $this->ocmDiscoveryService->getIncomingSignedRequest();
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming ocm request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// assuming that ocm request contains a json array
|
||||
$payload = $signedRequest?->getBody() ?? file_get_contents('php://input');
|
||||
try {
|
||||
$payload = ($payload) ? json_decode($payload, true, 512, JSON_THROW_ON_ERROR) : null;
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->debug('json decode error', ['exception' => $e]);
|
||||
$payload = null;
|
||||
}
|
||||
|
||||
$event = new OCMEndpointRequestEvent(
|
||||
$this->request->getMethod(),
|
||||
preg_replace('@/+@', '/', $ocmPath),
|
||||
$payload,
|
||||
$signedRequest?->getOrigin()
|
||||
);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
return $event->getResponse() ?? new DataResponse('', Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,8 +11,6 @@ use NCU\Federation\ISignedCloudFederationProvider;
|
|||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\IncomingRequestException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\SignatureException;
|
||||
use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
|
||||
use NCU\Security\Signature\IIncomingSignedRequest;
|
||||
use NCU\Security\Signature\ISignatureManager;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
|
|
@ -44,6 +42,7 @@ use OCP\IGroupManager;
|
|||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -74,8 +73,8 @@ class RequestHandlerController extends Controller {
|
|||
private readonly IAppConfig $appConfig,
|
||||
private ICloudFederationFactory $factory,
|
||||
private ICloudIdManager $cloudIdManager,
|
||||
private readonly IOCMDiscoveryService $ocmDiscoveryService,
|
||||
private readonly ISignatureManager $signatureManager,
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
private ITimeFactory $timeFactory,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
|
|
@ -108,9 +107,9 @@ class RequestHandlerController extends Controller {
|
|||
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is signed and well signed, no exceptions are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$signedRequest = $this->ocmDiscoveryService->getIncomingSignedRequest();
|
||||
$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
|
|
@ -360,7 +359,7 @@ class RequestHandlerController extends Controller {
|
|||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$signedRequest = $this->ocmDiscoveryService->getIncomingSignedRequest();
|
||||
$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
|
|
@ -434,37 +433,6 @@ class RequestHandlerController extends Controller {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns signed request if available.
|
||||
* throw an exception:
|
||||
* - if request is signed, but wrongly signed
|
||||
* - if request is not signed but instance is configured to only accept signed ocm request
|
||||
*
|
||||
* @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
|
||||
* @throws IncomingRequestException
|
||||
*/
|
||||
private function getSignedRequest(): ?IIncomingSignedRequest {
|
||||
try {
|
||||
$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
|
||||
$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
|
||||
return $signedRequest;
|
||||
} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
|
||||
$this->logger->debug('remote does not support signed request', ['exception' => $e]);
|
||||
// remote does not support signed request.
|
||||
// currently we still accept unsigned request until lazy appconfig
|
||||
// core.enforce_signed_ocm_request is set to true (default: false)
|
||||
if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
|
||||
$this->logger->notice('ignored unsigned request', ['exception' => $e]);
|
||||
throw new IncomingRequestException('Unsigned request');
|
||||
}
|
||||
} catch (SignatureException $e) {
|
||||
$this->logger->warning('wrongly signed request', ['exception' => $e]);
|
||||
throw new IncomingRequestException('Invalid signature');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* confirm that the value related to $key entry from the payload is in format userid@hostname
|
||||
* and compare hostname with the origin of the signed request.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ declare(strict_types=1);
|
|||
namespace OCA\CloudFederationApi\Tests;
|
||||
|
||||
use NCU\Security\Signature\ISignatureManager;
|
||||
use OC\OCM\OCMSignatoryManager;
|
||||
use OCA\CloudFederationAPI\Config;
|
||||
use OCA\CloudFederationAPI\Controller\RequestHandlerController;
|
||||
use OCA\CloudFederationAPI\Db\FederatedInvite;
|
||||
|
|
@ -29,6 +28,7 @@ use OCP\IRequest;
|
|||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
|
@ -45,10 +45,11 @@ class RequestHandlerControllerTest extends TestCase {
|
|||
private FederatedInviteMapper&MockObject $federatedInviteMapper;
|
||||
private AddressHandler&MockObject $addressHandler;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
|
||||
private ICloudFederationFactory&MockObject $cloudFederationFactory;
|
||||
private ICloudIdManager&MockObject $cloudIdManager;
|
||||
private IOCMDiscoveryService&MockObject $discoveryService;
|
||||
private ISignatureManager&MockObject $signatureManager;
|
||||
private OCMSignatoryManager&MockObject $signatoryManager;
|
||||
private ITimeFactory&MockObject $timeFactory;
|
||||
|
||||
private RequestHandlerController $requestHandlerController;
|
||||
|
|
@ -69,8 +70,8 @@ class RequestHandlerControllerTest extends TestCase {
|
|||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->cloudFederationFactory = $this->createMock(ICloudFederationFactory::class);
|
||||
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
|
||||
$this->discoveryService = $this->createMock(IOCMDiscoveryService::class);
|
||||
$this->signatureManager = $this->createMock(ISignatureManager::class);
|
||||
$this->signatoryManager = $this->createMock(OCMSignatoryManager::class);
|
||||
$this->timeFactory = $this->createMock(ITimeFactory::class);
|
||||
|
||||
$this->requestHandlerController = new RequestHandlerController(
|
||||
|
|
@ -88,8 +89,8 @@ class RequestHandlerControllerTest extends TestCase {
|
|||
$this->appConfig,
|
||||
$this->cloudFederationFactory,
|
||||
$this->cloudIdManager,
|
||||
$this->discoveryService,
|
||||
$this->signatureManager,
|
||||
$this->signatoryManager,
|
||||
$this->timeFactory,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -718,9 +718,14 @@ return array(
|
|||
'OCP\\Notification\\InvalidValueException' => $baseDir . '/lib/public/Notification/InvalidValueException.php',
|
||||
'OCP\\Notification\\NotificationPreloadReason' => $baseDir . '/lib/public/Notification/NotificationPreloadReason.php',
|
||||
'OCP\\Notification\\UnknownNotificationException' => $baseDir . '/lib/public/Notification/UnknownNotificationException.php',
|
||||
'OCP\\OCM\\Enum\\ParamType' => $baseDir . '/lib/public/OCM/Enum/ParamType.php',
|
||||
'OCP\\OCM\\Events\\LocalOCMDiscoveryEvent' => $baseDir . '/lib/public/OCM/Events/LocalOCMDiscoveryEvent.php',
|
||||
'OCP\\OCM\\Events\\OCMEndpointRequestEvent' => $baseDir . '/lib/public/OCM/Events/OCMEndpointRequestEvent.php',
|
||||
'OCP\\OCM\\Events\\ResourceTypeRegisterEvent' => $baseDir . '/lib/public/OCM/Events/ResourceTypeRegisterEvent.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMArgumentException' => $baseDir . '/lib/public/OCM/Exceptions/OCMArgumentException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMCapabilityException' => $baseDir . '/lib/public/OCM/Exceptions/OCMCapabilityException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMProviderException' => $baseDir . '/lib/public/OCM/Exceptions/OCMProviderException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMRequestException' => $baseDir . '/lib/public/OCM/Exceptions/OCMRequestException.php',
|
||||
'OCP\\OCM\\ICapabilityAwareOCMProvider' => $baseDir . '/lib/public/OCM/ICapabilityAwareOCMProvider.php',
|
||||
'OCP\\OCM\\IOCMDiscoveryService' => $baseDir . '/lib/public/OCM/IOCMDiscoveryService.php',
|
||||
'OCP\\OCM\\IOCMProvider' => $baseDir . '/lib/public/OCM/IOCMProvider.php',
|
||||
|
|
|
|||
|
|
@ -759,9 +759,14 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Notification\\InvalidValueException' => __DIR__ . '/../../..' . '/lib/public/Notification/InvalidValueException.php',
|
||||
'OCP\\Notification\\NotificationPreloadReason' => __DIR__ . '/../../..' . '/lib/public/Notification/NotificationPreloadReason.php',
|
||||
'OCP\\Notification\\UnknownNotificationException' => __DIR__ . '/../../..' . '/lib/public/Notification/UnknownNotificationException.php',
|
||||
'OCP\\OCM\\Enum\\ParamType' => __DIR__ . '/../../..' . '/lib/public/OCM/Enum/ParamType.php',
|
||||
'OCP\\OCM\\Events\\LocalOCMDiscoveryEvent' => __DIR__ . '/../../..' . '/lib/public/OCM/Events/LocalOCMDiscoveryEvent.php',
|
||||
'OCP\\OCM\\Events\\OCMEndpointRequestEvent' => __DIR__ . '/../../..' . '/lib/public/OCM/Events/OCMEndpointRequestEvent.php',
|
||||
'OCP\\OCM\\Events\\ResourceTypeRegisterEvent' => __DIR__ . '/../../..' . '/lib/public/OCM/Events/ResourceTypeRegisterEvent.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMArgumentException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMArgumentException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMCapabilityException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMCapabilityException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMProviderException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMProviderException.php',
|
||||
'OCP\\OCM\\Exceptions\\OCMRequestException' => __DIR__ . '/../../..' . '/lib/public/OCM/Exceptions/OCMRequestException.php',
|
||||
'OCP\\OCM\\ICapabilityAwareOCMProvider' => __DIR__ . '/../../..' . '/lib/public/OCM/ICapabilityAwareOCMProvider.php',
|
||||
'OCP\\OCM\\IOCMDiscoveryService' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMDiscoveryService.php',
|
||||
'OCP\\OCM\\IOCMProvider' => __DIR__ . '/../../..' . '/lib/public/OCM/IOCMProvider.php',
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ class RouteParser {
|
|||
$root = $this->buildRootPrefix($route, $appName, $routeNamePrefix);
|
||||
|
||||
$url = $root . '/' . ltrim($route['url'], '/');
|
||||
$verb = strtoupper($route['verb'] ?? 'GET');
|
||||
|
||||
$split = explode('#', $name, 3);
|
||||
if (count($split) !== 2) {
|
||||
|
|
@ -95,7 +94,7 @@ class RouteParser {
|
|||
$routeName = strtolower($routeNamePrefix . $appName . '.' . $controller . '.' . $action . $postfix);
|
||||
|
||||
$routeObject = new Route($url);
|
||||
$routeObject->method($verb);
|
||||
$routeObject->method($route['verb'] ?? 'GET');
|
||||
|
||||
// optionally register requirements for route. This is used to
|
||||
// tell the route parser how url parameters should be matched
|
||||
|
|
@ -174,7 +173,6 @@ class RouteParser {
|
|||
$url = $root . '/' . ltrim($config['url'], '/');
|
||||
$method = $action['name'];
|
||||
|
||||
$verb = strtoupper($action['verb'] ?? 'GET');
|
||||
$collectionAction = $action['on-collection'] ?? false;
|
||||
if (!$collectionAction) {
|
||||
$url .= '/{id}';
|
||||
|
|
@ -188,7 +186,7 @@ class RouteParser {
|
|||
$routeName = $routeNamePrefix . $appName . '.' . strtolower($resource) . '.' . $method;
|
||||
|
||||
$route = new Route($url);
|
||||
$route->method($verb);
|
||||
$route->method($action['verb'] ?? 'GET');
|
||||
|
||||
$route->defaults(['caller' => [$appName, $controllerName, $actionName]]);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use OCP\Http\Client\IClientService;
|
|||
use OCP\Http\Client\IResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\OCM\Exceptions\OCMCapabilityException;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -107,7 +108,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
|||
$cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
|
||||
try {
|
||||
try {
|
||||
$response = $this->postOcmPayload($cloudID->getRemote(), '/shares', json_encode($share->getShare()));
|
||||
$response = $this->postOcmPayload($cloudID->getRemote(), '/shares', $share->getShare());
|
||||
} catch (OCMProviderException) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -138,7 +139,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
|||
$cloudID = $this->cloudIdManager->resolveCloudId($share->getShareWith());
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
return $this->postOcmPayload($cloudID->getRemote(), '/shares', json_encode($share->getShare()), $client);
|
||||
return $this->postOcmPayload($cloudID->getRemote(), '/shares', $share->getShare(), $client);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while sending share to federation server: ' . $e->getMessage(), ['exception' => $e]);
|
||||
try {
|
||||
|
|
@ -158,7 +159,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
|||
public function sendNotification($url, ICloudFederationNotification $notification) {
|
||||
try {
|
||||
try {
|
||||
$response = $this->postOcmPayload($url, '/notifications', json_encode($notification->getMessage()));
|
||||
$response = $this->postOcmPayload($url, '/notifications', $notification->getMessage());
|
||||
} catch (OCMProviderException) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -183,7 +184,7 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
|||
public function sendCloudNotification(string $url, ICloudFederationNotification $notification): IResponse {
|
||||
$client = $this->httpClientService->newClient();
|
||||
try {
|
||||
return $this->postOcmPayload($url, '/notifications', json_encode($notification->getMessage()), $client);
|
||||
return $this->postOcmPayload($url, '/notifications', $notification->getMessage(), $client);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while sending notification to federation server: ' . $e->getMessage(), ['exception' => $e]);
|
||||
try {
|
||||
|
|
@ -204,51 +205,18 @@ class CloudFederationProviderManager implements ICloudFederationProviderManager
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $cloudId
|
||||
* @param string $uri
|
||||
* @param string $payload
|
||||
*
|
||||
* @return IResponse
|
||||
* @throws OCMCapabilityException
|
||||
* @throws OCMProviderException
|
||||
*/
|
||||
private function postOcmPayload(string $cloudId, string $uri, string $payload, ?IClient $client = null): IResponse {
|
||||
$ocmProvider = $this->discoveryService->discover($cloudId);
|
||||
$uri = $ocmProvider->getEndPoint() . '/' . ltrim($uri, '/');
|
||||
$client = $client ?? $this->httpClientService->newClient();
|
||||
return $client->post($uri, $this->prepareOcmPayload($uri, $payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param string $payload
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareOcmPayload(string $uri, string $payload): array {
|
||||
$payload = array_merge($this->getDefaultRequestOptions(), ['body' => $payload]);
|
||||
|
||||
if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)
|
||||
&& $this->signatoryManager->getRemoteSignatory($this->signatureManager->extractIdentityFromUri($uri)) === null) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
'post', $uri
|
||||
);
|
||||
}
|
||||
|
||||
return $signedPayload ?? $payload;
|
||||
}
|
||||
|
||||
private function getDefaultRequestOptions(): array {
|
||||
return [
|
||||
'headers' => ['content-type' => 'application/json'],
|
||||
'timeout' => 10,
|
||||
'connect_timeout' => 10,
|
||||
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
|
||||
];
|
||||
private function postOcmPayload(string $cloudId, string $uri, array $payload, ?IClient $client = null): IResponse {
|
||||
return $this->discoveryService->requestRemoteOcmEndpoint(
|
||||
null,
|
||||
$cloudId,
|
||||
$uri,
|
||||
$payload,
|
||||
'post',
|
||||
$client,
|
||||
['verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ namespace OC\OCM\Model;
|
|||
use NCU\Security\Signature\Model\Signatory;
|
||||
use OCP\OCM\Exceptions\OCMArgumentException;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
use OCP\OCM\ICapabilityAwareOCMProvider;
|
||||
use OCP\OCM\IOCMProvider;
|
||||
use OCP\OCM\IOCMResource;
|
||||
|
||||
/**
|
||||
* @since 28.0.0
|
||||
*/
|
||||
class OCMProvider implements ICapabilityAwareOCMProvider {
|
||||
class OCMProvider implements IOCMProvider {
|
||||
private bool $enabled = false;
|
||||
private string $apiVersion = '';
|
||||
private string $inviteAcceptDialog = '';
|
||||
|
|
@ -124,12 +124,10 @@ class OCMProvider implements ICapabilityAwareOCMProvider {
|
|||
* @return $this
|
||||
*/
|
||||
public function setCapabilities(array $capabilities): static {
|
||||
foreach ($capabilities as $value) {
|
||||
if (!in_array($value, $this->capabilities)) {
|
||||
array_push($this->capabilities, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$this->capabilities = array_unique(array_merge(
|
||||
$this->capabilities,
|
||||
array_map([$this, 'normalizeCapability'], $capabilities)
|
||||
));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +137,20 @@ class OCMProvider implements ICapabilityAwareOCMProvider {
|
|||
public function getCapabilities(): array {
|
||||
return $this->capabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $capability
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCapability(string $capability): bool {
|
||||
return (in_array($this->normalizeCapability($capability), $this->capabilities, true));
|
||||
}
|
||||
|
||||
private function normalizeCapability(string $capability): string {
|
||||
// since ocm 1.2, removing leading slashes from capabilities
|
||||
return strtolower(ltrim($capability, '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new resource to later add it with {@see IOCMProvider::addResourceType()}
|
||||
* @return IOCMResource
|
||||
|
|
|
|||
|
|
@ -9,35 +9,48 @@ declare(strict_types=1);
|
|||
|
||||
namespace OC\OCM;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use JsonException;
|
||||
use NCU\Security\Signature\Exceptions\IdentityNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\IncomingRequestException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryException;
|
||||
use NCU\Security\Signature\Exceptions\SignatoryNotFoundException;
|
||||
use NCU\Security\Signature\Exceptions\SignatureException;
|
||||
use NCU\Security\Signature\Exceptions\SignatureNotFoundException;
|
||||
use NCU\Security\Signature\IIncomingSignedRequest;
|
||||
use NCU\Security\Signature\ISignatureManager;
|
||||
use OC\Core\AppInfo\ConfigLexicon;
|
||||
use OC\OCM\Model\OCMProvider;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IConfig;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\OCM\Events\LocalOCMDiscoveryEvent;
|
||||
use OCP\OCM\Events\ResourceTypeRegisterEvent;
|
||||
use OCP\OCM\Exceptions\OCMCapabilityException;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
use OCP\OCM\ICapabilityAwareOCMProvider;
|
||||
use OCP\OCM\Exceptions\OCMRequestException;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use OCP\OCM\IOCMProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @since 28.0.0
|
||||
*/
|
||||
class OCMDiscoveryService implements IOCMDiscoveryService {
|
||||
#[Consumable(since: '28.0.0')]
|
||||
final class OCMDiscoveryService implements IOCMDiscoveryService {
|
||||
private ICache $cache;
|
||||
public const API_VERSION = '1.1.0';
|
||||
|
||||
private ?ICapabilityAwareOCMProvider $localProvider = null;
|
||||
/** @var array<string, ICapabilityAwareOCMProvider> */
|
||||
private ?IOCMProvider $localProvider = null;
|
||||
/** @var array<string, IOCMProvider> */
|
||||
private array $remoteProviders = [];
|
||||
|
||||
public function __construct(
|
||||
|
|
@ -47,20 +60,25 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
protected IConfig $config,
|
||||
private IAppConfig $appConfig,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private OCMSignatoryManager $ocmSignatoryManager,
|
||||
private readonly ISignatureManager $signatureManager,
|
||||
private readonly OCMSignatoryManager $signatoryManager,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
$this->cache = $cacheFactory->createDistributed('ocm-discovery');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $remote
|
||||
* @param bool $skipCache
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return ICapabilityAwareOCMProvider
|
||||
* @throws OCMProviderException
|
||||
* @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): ICapabilityAwareOCMProvider {
|
||||
public function discover(string $remote, bool $skipCache = false): IOCMProvider {
|
||||
$remote = rtrim($remote, '/');
|
||||
if (!str_starts_with($remote, 'http://') && !str_starts_with($remote, 'https://')) {
|
||||
// if scheme not specified, we test both;
|
||||
|
|
@ -138,7 +156,6 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
throw $exception;
|
||||
}
|
||||
|
||||
|
||||
throw new OCMProviderException('invalid remote ocm endpoint');
|
||||
} catch (JsonException|OCMProviderException) {
|
||||
$this->cache->set($remote, false, 5 * 60);
|
||||
|
|
@ -154,9 +171,15 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return ICapabilityAwareOCMProvider
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param bool $fullDetails complete details, including public keys.
|
||||
* Set to FALSE for client (capabilities) purpose.
|
||||
*
|
||||
* @return IOCMProvider
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getLocalOCMProvider(bool $fullDetails = true): ICapabilityAwareOCMProvider {
|
||||
public function getLocalOCMProvider(bool $fullDetails = true): IOCMProvider {
|
||||
if ($this->localProvider !== null) {
|
||||
return $this->localProvider;
|
||||
}
|
||||
|
|
@ -176,7 +199,7 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
$provider->setEnabled(true);
|
||||
$provider->setApiVersion(self::API_VERSION);
|
||||
$provider->setEndPoint(substr($url, 0, $pos));
|
||||
$provider->setCapabilities(['/invite-accepted', '/notifications', '/shares']);
|
||||
$provider->setCapabilities(['invite-accepted', 'notifications', 'shares']);
|
||||
|
||||
// The inviteAcceptDialog is available from the contacts app, if this config value is set
|
||||
$inviteAcceptDialog = $this->appConfig->getValueString('core', ConfigLexicon::OCM_INVITE_ACCEPT_DIALOG);
|
||||
|
|
@ -198,7 +221,7 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
* @experimental 31.0.0
|
||||
* @psalm-suppress UndefinedInterfaceMethod
|
||||
*/
|
||||
$provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
|
||||
$provider->setSignatory($this->signatoryManager->getLocalSignatory());
|
||||
} else {
|
||||
$this->logger->debug('ocm public key feature disabled');
|
||||
}
|
||||
|
|
@ -207,6 +230,10 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
}
|
||||
}
|
||||
|
||||
$event = new LocalOCMDiscoveryEvent($provider);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
// deprecated since 33.0.0
|
||||
$event = new ResourceTypeRegisterEvent($provider);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
|
||||
|
|
@ -214,4 +241,136 @@ class OCMDiscoveryService implements IOCMDiscoveryService {
|
|||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
|
||||
* @throws IncomingRequestException
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getIncomingSignedRequest(): ?IIncomingSignedRequest {
|
||||
try {
|
||||
$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
|
||||
$this->logger->debug('signed request available', ['signedRequest' => $signedRequest]);
|
||||
return $signedRequest;
|
||||
} catch (SignatureNotFoundException|SignatoryNotFoundException $e) {
|
||||
$this->logger->debug('remote does not support signed request', ['exception' => $e]);
|
||||
// remote does not support signed request.
|
||||
// currently we still accept unsigned request until lazy appconfig
|
||||
// core.enforce_signed_ocm_request is set to true (default: false)
|
||||
if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)) {
|
||||
$this->logger->notice('ignored unsigned request', ['exception' => $e]);
|
||||
throw new IncomingRequestException('Unsigned request');
|
||||
}
|
||||
} catch (SignatureException $e) {
|
||||
$this->logger->warning('wrongly signed request', ['exception' => $e]);
|
||||
throw new IncomingRequestException('Invalid signature');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string|null $capability when not NULL, method will throw
|
||||
* {@see OCMCapabilityException}
|
||||
* if remote does not support the capability
|
||||
* @param string $remote remote ocm cloud id
|
||||
* @param string $ocmSubPath path to reach, complementing the ocm endpoint extracted
|
||||
* from remote discovery data
|
||||
* @param array|null $payload payload attached to the request
|
||||
* @param string $method method to use ('get', 'post', 'put', 'delete')
|
||||
* @param IClient|null $client NULL to use default {@see IClient}
|
||||
* @param array|null $options options related to IClient
|
||||
* @param bool $signed FALSE to not auth the request
|
||||
*
|
||||
* @throws OCMCapabilityException if remote does not support $capability
|
||||
* @throws OCMProviderException if remote ocm provider is disabled or invalid data returned
|
||||
* @throws OCMRequestException on internal issue
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function requestRemoteOcmEndpoint(
|
||||
?string $capability,
|
||||
string $remote,
|
||||
string $ocmSubPath,
|
||||
?array $payload = null,
|
||||
string $method = 'get',
|
||||
?IClient $client = null,
|
||||
?array $options = null,
|
||||
bool $signed = true,
|
||||
): IResponse {
|
||||
$ocmProvider = $this->discover($remote);
|
||||
if (!$ocmProvider->isEnabled()) {
|
||||
throw new OCMProviderException('remote ocm provider is disabled');
|
||||
}
|
||||
|
||||
if ($capability !== null && !$ocmProvider->hasCapability($capability)) {
|
||||
throw new OCMCapabilityException(sprintf('remote does not support %s', $capability));
|
||||
}
|
||||
|
||||
$uri = $ocmProvider->getEndPoint() . '/' . ltrim($ocmSubPath, '/');
|
||||
$client = $client ?? $this->clientService->newClient();
|
||||
|
||||
try {
|
||||
$body = json_encode($payload ?? [], JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->warning('payload could not be converted to JSON', ['exception' => $e]);
|
||||
throw new OCMRequestException('ocm payload issue');
|
||||
}
|
||||
|
||||
try {
|
||||
$options = $options ?? [];
|
||||
return match (strtolower($method)) {
|
||||
'get' => $client->get($uri, $this->prepareOcmPayload($uri, 'get', $options, $body, $signed)),
|
||||
'post' => $client->post($uri, $this->prepareOcmPayload($uri, 'post', $options, $body, $signed)),
|
||||
'put' => $client->put($uri, $this->prepareOcmPayload($uri, 'put', $options, $body, $signed)),
|
||||
'delete' => $client->delete($uri, $this->prepareOcmPayload($uri, 'delete', $options, $body, $signed)),
|
||||
default => throw new OCMRequestException('unknown method'),
|
||||
};
|
||||
} catch (OCMRequestException $e) {
|
||||
throw $e;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->warning('error while requesting remote ocm endpoint', ['exception' => $e]);
|
||||
throw new OCMProviderException('error while requesting remote endpoint');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add entries to the payload to auth the whole request
|
||||
*
|
||||
* @throws OCMProviderException
|
||||
* @return array
|
||||
*/
|
||||
private function prepareOcmPayload(string $uri, string $method, array $options, string $payload, bool $signed): array {
|
||||
$payload = array_merge($this->generateRequestOptions($options), ['body' => $payload]);
|
||||
if (!$signed) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
if ($this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_ENFORCED, lazy: true)
|
||||
&& $this->signatoryManager->getRemoteSignatory($this->signatureManager->extractIdentityFromUri($uri)) === null) {
|
||||
throw new OCMProviderException('remote endpoint does not support signed request');
|
||||
}
|
||||
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
|
||||
$this->signatoryManager,
|
||||
$payload,
|
||||
$method, $uri
|
||||
);
|
||||
}
|
||||
|
||||
return $signedPayload ?? $payload;
|
||||
}
|
||||
|
||||
private function generateRequestOptions(array $options): array {
|
||||
return array_merge(
|
||||
[
|
||||
'headers' => ['content-type' => 'application/json'],
|
||||
'timeout' => 5,
|
||||
'connect_timeout' => 5,
|
||||
],
|
||||
$options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Route extends SymfonyRoute implements IRoute {
|
|||
/**
|
||||
* Specify the method when this route is to be used
|
||||
*
|
||||
* @param string $method HTTP method (uppercase)
|
||||
* @param string|array $method HTTP method
|
||||
* @return \OC\Route\Route
|
||||
*/
|
||||
public function method($method) {
|
||||
|
|
|
|||
|
|
@ -206,9 +206,7 @@ use OCP\Lockdown\ILockdownManager;
|
|||
use OCP\Log\ILogFactory;
|
||||
use OCP\Mail\IEmailValidator;
|
||||
use OCP\Mail\IMailer;
|
||||
use OCP\OCM\ICapabilityAwareOCMProvider;
|
||||
use OCP\OCM\IOCMDiscoveryService;
|
||||
use OCP\OCM\IOCMProvider;
|
||||
use OCP\Preview\IMimeIconProvider;
|
||||
use OCP\Profile\IProfileManager;
|
||||
use OCP\Profiler\IProfiler;
|
||||
|
|
@ -1245,9 +1243,9 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
|
||||
$this->registerAlias(IPhoneNumberUtil::class, PhoneNumberUtil::class);
|
||||
|
||||
// there is no reason for having OCMProvider as a Service
|
||||
$this->registerDeprecatedAlias(ICapabilityAwareOCMProvider::class, OCMProvider::class);
|
||||
$this->registerDeprecatedAlias(IOCMProvider::class, OCMProvider::class);
|
||||
// there is no reason for having OCMProvider as a Service (marked as deprecated since 32.0.0)
|
||||
$this->registerDeprecatedAlias(\OCP\OCM\ICapabilityAwareOCMProvider::class, OCMProvider::class);
|
||||
$this->registerDeprecatedAlias(\OCP\OCM\IOCMProvider::class, OCMProvider::class);
|
||||
|
||||
$this->registerAlias(ISetupCheckManager::class, SetupCheckManager::class);
|
||||
|
||||
|
|
|
|||
24
lib/public/OCM/Enum/ParamType.php
Normal file
24
lib/public/OCM/Enum/ParamType.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\OCM\Enum;
|
||||
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
|
||||
/**
|
||||
* Expected type for each argument contained in the ocm path
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
#[Consumable(since: '33.0.0')]
|
||||
enum ParamType: string {
|
||||
case STRING = 'string';
|
||||
case INT = 'int';
|
||||
case FLOAT = 'float';
|
||||
case BOOL = 'bool';
|
||||
}
|
||||
57
lib/public/OCM/Events/LocalOCMDiscoveryEvent.php
Normal file
57
lib/public/OCM/Events/LocalOCMDiscoveryEvent.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\OCM\Events;
|
||||
|
||||
use OCP\AppFramework\Attribute\Listenable;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\OCM\IOCMProvider;
|
||||
|
||||
/**
|
||||
* Use this event to register additional resources before the API returns
|
||||
* them in the OCM provider list and capability
|
||||
*
|
||||
* @since 33.0.0
|
||||
*
|
||||
*/
|
||||
#[Listenable(since: '33.0.0')]
|
||||
class LocalOCMDiscoveryEvent extends Event {
|
||||
/**
|
||||
* @param IOCMProvider $provider
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IOCMProvider $provider,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new OCM capability to the discovery data of local instance
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function addCapability(string $capability): void {
|
||||
$this->provider->setCapabilities([$capability]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param list<string> $shareTypes List of supported share recipients, e.g. 'user', 'group', …
|
||||
* @param array<string, string> $protocols List of supported protocols and their location,
|
||||
* e.g. ['webdav' => '/remote.php/webdav/']
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function registerResourceType(string $name, array $shareTypes, array $protocols): void {
|
||||
$resourceType = $this->provider->createNewResourceType();
|
||||
$resourceType->setName($name)
|
||||
->setShareTypes($shareTypes)
|
||||
->setProtocols($protocols);
|
||||
$this->provider->addResourceType($resourceType);
|
||||
}
|
||||
}
|
||||
170
lib/public/OCM/Events/OCMEndpointRequestEvent.php
Normal file
170
lib/public/OCM/Events/OCMEndpointRequestEvent.php
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\OCM\Events;
|
||||
|
||||
use OCP\AppFramework\Attribute\Listenable;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\OCM\Enum\ParamType;
|
||||
|
||||
/**
|
||||
* Use this event to catch and manage incoming OCM request
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
#[Listenable(since: '33.0.0')]
|
||||
final class OCMEndpointRequestEvent extends Event {
|
||||
private ?Response $response = null;
|
||||
private string $capability = '';
|
||||
private string $path; // does not start with a slash '/'
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly string $method,
|
||||
string $path,
|
||||
private readonly ?array $payload = null,
|
||||
private readonly ?string $remote = null,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$path = trim($path, '/');
|
||||
if (!str_contains($path, '/')) {
|
||||
$this->capability = $path;
|
||||
$path = '';
|
||||
} else {
|
||||
[$this->capability, $path] = explode('/', $path, 2);
|
||||
}
|
||||
$this->path = $path ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the first parameter of the sub-path (post-/ocm/) from the request
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getRequestedCapability(): string {
|
||||
return $this->capability;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the method used
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getUsedMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the sub-path (post-/ocm/) of the request
|
||||
* will start with a slash ('/')
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getPath(): string {
|
||||
return '/' . $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of parameters from the request, post-'capability'
|
||||
*
|
||||
* If no ParamType is specified as parameter of the method, the returned array
|
||||
* will contain all entries (all string).
|
||||
*
|
||||
* If one or multiple ParamType are set:
|
||||
* - the returned array will contain as many entries as the number of ParamType,
|
||||
* - each value from the returned array will be typed based on set ParamType,
|
||||
* - if ParamType cannot be applied (i.e., only alphabetic chars while expecting
|
||||
* integer), value will be NULL,
|
||||
* - if missing elements to the request path, missing entries will be NULL,
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getArgs(ParamType ...$params): array {
|
||||
if ($this->path === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$args = explode('/', $this->path);
|
||||
if (empty($params)) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$typedArgs = [];
|
||||
$i = 0;
|
||||
foreach ($params as $param) {
|
||||
if (($args[$i] ?? null) === null) {
|
||||
break;
|
||||
}
|
||||
$typedArgs[] = match($param) {
|
||||
ParamType::STRING => $args[$i],
|
||||
ParamType::INT => (is_numeric($args[$i]) && ((int)$args[$i] == (float)$args[$i])) ? (int)$args[$i] : null,
|
||||
ParamType::FLOAT => (is_numeric($args[$i])) ? (float)$args[$i] : null,
|
||||
ParamType::BOOL => in_array(strtolower($args[$i]), ['1', 'true', 'yes', 'on'], true),
|
||||
};
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $typedArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the number of parameters found in the subpath
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getArgsCount(): int {
|
||||
return count($this->getArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the payload attached to the request
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getPayload(): array {
|
||||
return $this->payload ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool TRUE if request is signed
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function isSigned(): bool {
|
||||
return ($this->getRemote() !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the origin of the request, if signed.
|
||||
*
|
||||
* @return string|null NULL if request is not authed
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getRemote(): ?string {
|
||||
return $this->remote;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the Response to the Request to be sent to requester
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function setResponse(Response $response): void {
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getResponse(): ?Response {
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,13 @@ use OCP\OCM\IOCMProvider;
|
|||
* them in the OCM provider list and capability
|
||||
*
|
||||
* @since 28.0.0
|
||||
* @depecated 33.0.0 (use {@see LocalOCMDiscoveryEvent})
|
||||
*/
|
||||
class ResourceTypeRegisterEvent extends Event {
|
||||
/**
|
||||
* @param IOCMProvider $provider
|
||||
* @since 28.0.0
|
||||
* @depecated 33.0.0 (use {@see LocalOCMDiscoveryEvent})
|
||||
*/
|
||||
public function __construct(
|
||||
protected IOCMProvider $provider,
|
||||
|
|
@ -34,6 +36,7 @@ class ResourceTypeRegisterEvent extends Event {
|
|||
* @param array<string, string> $protocols List of supported protocols and their location,
|
||||
* e.g. ['webdav' => '/remote.php/webdav/']
|
||||
* @since 28.0.0
|
||||
* @depecated 33.0.0 (use {@see LocalOCMDiscoveryEvent})
|
||||
*/
|
||||
public function registerResourceType(string $name, array $shareTypes, array $protocols): void {
|
||||
$resourceType = $this->provider->createNewResourceType();
|
||||
|
|
|
|||
17
lib/public/OCM/Exceptions/OCMCapabilityException.php
Normal file
17
lib/public/OCM/Exceptions/OCMCapabilityException.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\OCM\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
class OCMCapabilityException extends Exception {
|
||||
}
|
||||
17
lib/public/OCM/Exceptions/OCMRequestException.php
Normal file
17
lib/public/OCM/Exceptions/OCMRequestException.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\OCM\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
class OCMRequestException extends Exception {
|
||||
}
|
||||
|
|
@ -12,49 +12,7 @@ namespace OCP\OCM;
|
|||
* Version 1.1 and 1.2 extensions to the Open Cloud Mesh Discovery API
|
||||
* @link https://github.com/cs3org/OCM-API/
|
||||
* @since 32.0.0
|
||||
* @deprecated 33.0.0 {@see IOCMProvider}
|
||||
*/
|
||||
interface ICapabilityAwareOCMProvider extends IOCMProvider {
|
||||
/**
|
||||
* get the capabilities
|
||||
*
|
||||
* @return array
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function getCapabilities(): array;
|
||||
|
||||
/**
|
||||
* get the provider name
|
||||
*
|
||||
* @return string
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function getProvider(): string;
|
||||
|
||||
/**
|
||||
* returns the invite accept dialog
|
||||
*
|
||||
* @return string
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function getInviteAcceptDialog(): string;
|
||||
|
||||
/**
|
||||
* set the capabilities
|
||||
*
|
||||
* @param array $capabilities
|
||||
*
|
||||
* @return $this
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function setCapabilities(array $capabilities): static;
|
||||
|
||||
/**
|
||||
* set the invite accept dialog
|
||||
*
|
||||
* @param string $inviteAcceptDialog
|
||||
*
|
||||
* @return $this
|
||||
* @since 32.0.0
|
||||
*/
|
||||
public function setInviteAcceptDialog(string $inviteAcceptDialog): static;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCP\OCM;
|
||||
|
||||
use NCU\Security\Signature\Exceptions\IncomingRequestException;
|
||||
use NCU\Security\Signature\IIncomingSignedRequest;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\Http\Client\IClient;
|
||||
use OCP\Http\Client\IResponse;
|
||||
use OCP\OCM\Events\LocalOCMDiscoveryEvent;
|
||||
use OCP\OCM\Exceptions\OCMCapabilityException;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
|
||||
/**
|
||||
|
|
@ -16,6 +23,7 @@ use OCP\OCM\Exceptions\OCMProviderException;
|
|||
*
|
||||
* @since 28.0.0
|
||||
*/
|
||||
#[Consumable(since: '28.0.0')]
|
||||
interface IOCMDiscoveryService {
|
||||
/**
|
||||
* Discover remote OCM services
|
||||
|
|
@ -23,10 +31,70 @@ interface IOCMDiscoveryService {
|
|||
* @param string $remote address of the remote provider
|
||||
* @param bool $skipCache ignore cache, refresh data
|
||||
*
|
||||
* @return ICapabilityAwareOCMProvider
|
||||
* @return IOCMProvider
|
||||
* @throws OCMProviderException if no valid discovery data can be returned
|
||||
* @since 28.0.0
|
||||
* @since 32.0.0 returns ICapabilityAwareOCMProvider instead of IOCMProvider
|
||||
* @since 33.0.0 returns IOCMProvider (rollback)
|
||||
*/
|
||||
public function discover(string $remote, bool $skipCache = false): ICapabilityAwareOCMProvider;
|
||||
public function discover(string $remote, bool $skipCache = false): IOCMProvider;
|
||||
|
||||
/**
|
||||
* return discovery data about local instance.
|
||||
*
|
||||
* will generate event {@see LocalOCMDiscoveryEvent} so that 3rd parties can define new resources.
|
||||
*
|
||||
* @param bool $fullDetails complete details, including public keys.
|
||||
* Set to FALSE for client (capabilities) purpose.
|
||||
*
|
||||
* @return IOCMProvider
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getLocalOCMProvider(bool $fullDetails = true): IOCMProvider;
|
||||
|
||||
/**
|
||||
* returns signed request if available.
|
||||
*
|
||||
* throw an exception:
|
||||
* - if request is signed, but wrongly signed
|
||||
* - if request is not signed but instance is configured to only accept signed ocm request
|
||||
*
|
||||
* @return IIncomingSignedRequest|null null if remote does not (and never did) support signed request
|
||||
* @throws IncomingRequestException
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getIncomingSignedRequest(): ?IIncomingSignedRequest;
|
||||
|
||||
/**
|
||||
* Request a remote OCM endpoint.
|
||||
*
|
||||
* Capability can be filtered out
|
||||
* The final path will be generated based on remote discovery.
|
||||
*
|
||||
* @param string|null $capability when not NULL, method will throw
|
||||
* {@see OCMCapabilityException}
|
||||
* if remote does not support the capability
|
||||
* @param string $remote remote ocm cloud id
|
||||
* @param string $ocmSubPath path to reach, complementing the ocm endpoint extracted
|
||||
* from remote discovery data
|
||||
* @param array|null $payload payload attached to the request
|
||||
* @param string $method method to use ('get', 'post', 'put', 'delete')
|
||||
* @param IClient|null $client NULL to use default {@see IClient}
|
||||
* @param array|null $options options related to IClient
|
||||
* @param bool $signed FALSE to not auth the request
|
||||
*
|
||||
* @throws OCMProviderException
|
||||
* @throws OCMCapabilityException if remote does not support $capability
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function requestRemoteOcmEndpoint(
|
||||
?string $capability,
|
||||
string $remote,
|
||||
string $ocmSubPath,
|
||||
?array $payload = null,
|
||||
string $method = 'get',
|
||||
?IClient $client = null,
|
||||
?array $options = null,
|
||||
bool $signed = true,
|
||||
): IResponse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
namespace OCP\OCM;
|
||||
|
||||
use JsonSerializable;
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\OCM\Exceptions\OCMArgumentException;
|
||||
use OCP\OCM\Exceptions\OCMProviderException;
|
||||
|
||||
|
|
@ -16,8 +17,8 @@ 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
|
||||
* @deprecated 32.0.0 Please use {@see \OCP\OCM\ICapabilityAwareOCMProvider}
|
||||
*/
|
||||
#[Consumable(since: '28.0.0')]
|
||||
interface IOCMProvider extends JsonSerializable {
|
||||
/**
|
||||
* enable OCM
|
||||
|
|
@ -108,6 +109,57 @@ interface IOCMProvider extends JsonSerializable {
|
|||
*/
|
||||
public function getResourceTypes(): array;
|
||||
|
||||
/**
|
||||
* get the capabilities
|
||||
*
|
||||
* @return array
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getCapabilities(): array;
|
||||
|
||||
/**
|
||||
* return if provider supports $capability
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function hasCapability(string $capability): bool;
|
||||
|
||||
/**
|
||||
* get the provider name
|
||||
*
|
||||
* @return string
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getProvider(): string;
|
||||
|
||||
/**
|
||||
* returns the invite accept dialog
|
||||
*
|
||||
* @return string
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getInviteAcceptDialog(): string;
|
||||
|
||||
/**
|
||||
* set the capabilities
|
||||
*
|
||||
* @param array $capabilities
|
||||
*
|
||||
* @return $this
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function setCapabilities(array $capabilities): static;
|
||||
|
||||
/**
|
||||
* set the invite accept dialog
|
||||
*
|
||||
* @param string $inviteAcceptDialog
|
||||
*
|
||||
* @return $this
|
||||
* @since 33.0.0
|
||||
*/
|
||||
|
||||
public function setInviteAcceptDialog(string $inviteAcceptDialog): static;
|
||||
/**
|
||||
* extract a specific string value from the listing of protocols, based on resource-name and protocol-name
|
||||
*
|
||||
|
|
|
|||
138
tests/lib/OCM/DiscoveryServiceTest.php
Normal file
138
tests/lib/OCM/DiscoveryServiceTest.php
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace Test\OCM;
|
||||
|
||||
use OC\AppFramework\Bootstrap\RegistrationContext;
|
||||
use OC\OCM\OCMDiscoveryService;
|
||||
use OCA\CloudFederationAPI\Controller\OCMRequestController;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IConfig;
|
||||
use OCP\OCM\Events\LocalOCMDiscoveryEvent;
|
||||
use OCP\OCM\Events\OCMEndpointRequestEvent;
|
||||
use OCP\Server;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionClass;
|
||||
use Test\OCM\Listeners\LocalOCMDiscoveryTestEvent;
|
||||
use Test\OCM\Listeners\OCMEndpointRequestTestEvent;
|
||||
use Test\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class DiscoveryServiceTest extends TestCase {
|
||||
private LoggerInterface $logger;
|
||||
private RegistrationContext $context;
|
||||
private IEventDispatcher $dispatcher;
|
||||
private OCMDiscoveryService $discoveryService;
|
||||
private IConfig $config;
|
||||
private OCMRequestController $requestController;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->context = Server::get(RegistrationContext::class);
|
||||
$this->dispatcher = Server::get(IEventDispatcher::class);
|
||||
$this->discoveryService = Server::get(OCMDiscoveryService::class);
|
||||
$this->config = Server::get(IConfig::class);
|
||||
|
||||
// reset $localProvider value between tests
|
||||
$reflection = new ReflectionClass($this->discoveryService);
|
||||
$localProvider = $reflection->getProperty('localProvider');
|
||||
$localProvider->setValue($this->discoveryService, null);
|
||||
|
||||
$this->requestController = Server::get(OCMRequestController::class);
|
||||
}
|
||||
|
||||
public static function dataTestOCMRequest(): array {
|
||||
return [
|
||||
['/inexistant-path/', 404, null],
|
||||
['/ocm-capability-test/', 404, null],
|
||||
['/ocm-capability-test/get', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get',
|
||||
'args' => ['get'],
|
||||
'totalArgs' => 1,
|
||||
'typedArgs' => ['get'],
|
||||
]
|
||||
],
|
||||
['/ocm-capability-test/get/10/', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get/10',
|
||||
'args' => ['get', '10'],
|
||||
'totalArgs' => 2,
|
||||
'typedArgs' => ['get', '10'],
|
||||
]
|
||||
],
|
||||
['/ocm-capability-test/get/random/10/', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get/random/10',
|
||||
'args' => ['get', 'random', '10'],
|
||||
'totalArgs' => 3,
|
||||
'typedArgs' => ['get', 'random', 10],
|
||||
]
|
||||
],
|
||||
['/ocm-capability-test/get/random/10/1', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get/random/10/1',
|
||||
'args' => ['get', 'random', '10', '1'],
|
||||
'totalArgs' => 4,
|
||||
'typedArgs' => ['get', 'random', 10, true],
|
||||
]
|
||||
],
|
||||
['/ocm-capability-test/get/random/10/true', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get/random/10/true',
|
||||
'args' => ['get', 'random', '10', 'true'],
|
||||
'totalArgs' => 4,
|
||||
'typedArgs' => ['get', 'random', 10, true],
|
||||
]
|
||||
],
|
||||
['/ocm-capability-test/get/random/10/true/42', 200,
|
||||
[
|
||||
'capability' => 'ocm-capability-test',
|
||||
'path' => '/get/random/10/true/42',
|
||||
'args' => ['get', 'random', '10', 'true', '42'],
|
||||
'totalArgs' => 5,
|
||||
'typedArgs' => ['get', 'random', 10, true, 42],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataTestOCMRequest')]
|
||||
public function testOCMRequest(string $path, int $expectedStatus, ?array $expectedResult): void {
|
||||
$this->context->for('ocm-request-app')->registerEventListener(OCMEndpointRequestEvent::class, OCMEndpointRequestTestEvent::class);
|
||||
$this->context->delegateEventListenerRegistrations($this->dispatcher);
|
||||
|
||||
$response = $this->requestController->manageOCMRequests($path);
|
||||
$this->assertSame($expectedStatus, $response->getStatus());
|
||||
if ($expectedResult !== null) {
|
||||
$this->assertSame($expectedResult, $response->getData());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function testLocalBaseCapability(): void {
|
||||
$local = $this->discoveryService->getLocalOCMProvider();
|
||||
$this->assertEmpty(array_diff(['notifications', 'shares'], $local->getCapabilities()));
|
||||
}
|
||||
|
||||
|
||||
public function testLocalAddedCapability(): void {
|
||||
$this->context->for('ocm-capability-app')->registerEventListener(LocalOCMDiscoveryEvent::class, LocalOCMDiscoveryTestEvent::class);
|
||||
$this->context->delegateEventListenerRegistrations($this->dispatcher);
|
||||
$local = $this->discoveryService->getLocalOCMProvider();
|
||||
$this->assertEmpty(array_diff(['notifications', 'shares', 'ocm-capability-test'], $local->getCapabilities()));
|
||||
}
|
||||
|
||||
}
|
||||
27
tests/lib/OCM/Listeners/LocalOCMDiscoveryTestEvent.php
Normal file
27
tests/lib/OCM/Listeners/LocalOCMDiscoveryTestEvent.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace Test\OCM\Listeners;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
|
||||
/** @template-implements IEventListener<\OCP\OCM\Events\LocalOCMDiscoveryEvent> */
|
||||
class LocalOCMDiscoveryTestEvent implements IEventListener {
|
||||
public function __construct(
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof \OCP\OCM\Events\LocalOCMDiscoveryEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->addCapability('ocm-capability-test');
|
||||
}
|
||||
}
|
||||
48
tests/lib/OCM/Listeners/OCMEndpointRequestTestEvent.php
Normal file
48
tests/lib/OCM/Listeners/OCMEndpointRequestTestEvent.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace Test\OCM\Listeners;
|
||||
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
|
||||
/** @template-implements IEventListener<\OCP\OCM\Events\OCMEndpointRequestEvent> */
|
||||
class OCMEndpointRequestTestEvent implements IEventListener {
|
||||
public function __construct(
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
if (!($event instanceof \OCP\OCM\Events\OCMEndpointRequestEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->getPath() === '/') {
|
||||
$event->setResponse(new Response(404));
|
||||
return;
|
||||
}
|
||||
|
||||
$event->setResponse(new DataResponse(
|
||||
[
|
||||
'capability' => $event->getRequestedCapability(),
|
||||
'path' => $event->getPath(),
|
||||
'args' => $event->getArgs(),
|
||||
'totalArgs' => $event->getArgsCount(),
|
||||
'typedArgs' => $event->getArgs(
|
||||
\OCP\OCM\Enum\ParamType::STRING,
|
||||
\OCP\OCM\Enum\ParamType::STRING,
|
||||
\OCP\OCM\Enum\ParamType::INT,
|
||||
\OCP\OCM\Enum\ParamType::BOOL,
|
||||
\OCP\OCM\Enum\ParamType::INT
|
||||
)
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue