Skip to content

Commit

Permalink
outgoing request
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <[email protected]>
  • Loading branch information
ArtificialOwl committed Jun 25, 2024
1 parent a6481e3 commit 58d3efd
Show file tree
Hide file tree
Showing 22 changed files with 385 additions and 123 deletions.
15 changes: 14 additions & 1 deletion apps/cloud_federation_api/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@

namespace OCA\CloudFederationAPI;

use OC\OCM\OCMSignatoryManager;
use OC\Security\Signature\Model\Signatory;
use OCP\Capabilities\ICapability;
use OCP\IURLGenerator;
use OCP\OCM\Exceptions\OCMArgumentException;
use OCP\OCM\IOCMProvider;
use OCP\Security\Signature\Exceptions\SignatoryException;
use OCP\Security\Signature\Model\ISignatory;
use Psr\Log\LoggerInterface;

class Capabilities implements ICapability {
public const API_VERSION = '1.0-proposal1';
public const API_VERSION = '1.1'; // informative, real version.

public function __construct(
private IURLGenerator $urlGenerator,
private IOCMProvider $provider,
private readonly OCMSignatoryManager $ocmSignatoryManager,
private readonly LoggerInterface $logger,
) {
}

Expand Down Expand Up @@ -60,6 +67,12 @@ public function getCapabilities() {

$this->provider->addResourceType($resource);

try {
$this->provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
} catch (SignatoryException $e) {
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
}

return ['ocm' => $this->provider->jsonSerialize()];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
namespace OCA\CloudFederationAPI\Controller;

use OC\OCM\OCMSignatoryManager;
use OCA\CloudFederationAPI\Config;
use OCA\CloudFederationAPI\ResponseDefinitions;
use OCP\AppFramework\Controller;
Expand All @@ -23,6 +24,8 @@
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Security\Signature\Exceptions\SignatureException;
use OCP\Security\Signature\ISignatureManager;
use OCP\Share\Exceptions\ShareNotFound;
use Psr\Log\LoggerInterface;

Expand All @@ -47,7 +50,9 @@ public function __construct(
private ICloudFederationProviderManager $cloudFederationProviderManager,
private Config $config,
private ICloudFederationFactory $factory,
private ICloudIdManager $cloudIdManager
private ICloudIdManager $cloudIdManager,
private readonly ISignatureManager $signatureManager,
private readonly OCMSignatoryManager $signatoryManager,
) {
parent::__construct($appName, $request);
}
Expand Down Expand Up @@ -77,6 +82,12 @@ public function __construct(
* 501: Share type or the resource type is not supported
*/
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
try {
$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
} catch (SignatureException $e) {
// impossible to confirm signature
}

// check if all required parameters are set
if ($shareWith === null ||
$name === null ||
Expand All @@ -99,6 +110,7 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
);
}


$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
if (!in_array($shareType, $supportedShareTypes)) {
return new JSONResponse(
Expand Down
5 changes: 4 additions & 1 deletion apps/files_sharing/lib/External/Cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ class Cache extends \OC\Files\Cache\Cache {
public function __construct($storage, ICloudId $cloudId) {
$this->cloudId = $cloudId;
$this->storage = $storage;
[, $remote] = explode('://', $cloudId->getRemote(), 2);
$remote = $cloudId->getRemote();
if (str_contains($remote, '://')) {
[, $remote] = explode('://', $cloudId->getRemote(), 2);

Check notice

Code scanning / Psalm

PossiblyUndefinedArrayOffset Note

Possibly undefined array key
}
$this->remote = $remote;
$this->remoteUser = $cloudId->getUser();
parent::__construct($storage);
Expand Down
11 changes: 6 additions & 5 deletions core/Controller/OCMController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
use OCP\AppFramework\Http\DataResponse;
use OCP\Capabilities\ICapability;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IRequest;
use OCP\Server;
Expand All @@ -29,7 +30,7 @@
class OCMController extends Controller {
public function __construct(
IRequest $request,
private IConfig $config,
private readonly IAppConfig $appConfig,
private LoggerInterface $logger
) {
parent::__construct('core', $request);
Expand All @@ -52,10 +53,10 @@ public function __construct(
public function discovery(): DataResponse {
try {
$cap = Server::get(
$this->config->getAppValue(
'core',
'ocm_providers',
'\OCA\CloudFederationAPI\Capabilities'
$this->appConfig->getValueString(
'core', 'ocm_providers',
\OCA\CloudFederationAPI\Capabilities::class,
lazy: true
)
);

Expand Down
2 changes: 1 addition & 1 deletion lib/private/AppFramework/Http/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ public function getCookie(string $key) {
*
* @throws \LogicException
*/
protected function getContent() {
public function getContent() {
// If the content can't be parsed into an array then return a stream resource.
if ($this->isPutStreamContent()) {
if ($this->content === false) {
Expand Down
25 changes: 17 additions & 8 deletions lib/private/Federation/CloudFederationProviderManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace OC\Federation;

use OC\AppFramework\Http;
use OC\OCM\OCMSignatoryManager;
use OCP\App\IAppManager;
use OCP\Federation\Exceptions\ProviderDoesNotExistsException;
use OCP\Federation\ICloudFederationNotification;
Expand All @@ -21,6 +22,7 @@
use OCP\IConfig;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMDiscoveryService;
use OCP\Security\Signature\ISignatureManager;
use Psr\Log\LoggerInterface;

/**
Expand All @@ -40,7 +42,9 @@ public function __construct(
private IClientService $httpClientService,
private ICloudIdManager $cloudIdManager,
private IOCMDiscoveryService $discoveryService,
private LoggerInterface $logger
private readonly ISignatureManager $signatureManager,
private readonly OCMSignatoryManager $signatoryManager,
private LoggerInterface $logger,
) {
}

Expand Down Expand Up @@ -106,13 +110,18 @@ public function sendShare(ICloudFederationShare $share) {

$client = $this->httpClientService->newClient();
try {
$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,
]);
$uri = $ocmProvider->getEndPoint() . '/shares';
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
$this->signatoryManager,
[
'body' => json_encode($share->getShare()),
'headers' => ['content-type' => 'application/json'],
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
'timeout' => 10,
'connect_timeout' => 10,
],
'post', $uri);
$response = $client->post($uri, $signedPayload);

if ($response->getStatusCode() === Http::STATUS_CREATED) {
$result = json_decode($response->getBody(), true);
Expand Down
25 changes: 22 additions & 3 deletions lib/private/OCM/Model/OCMProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@

namespace OC\OCM\Model;

use OC\Security\Signature\Model\Signatory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\OCM\Events\ResourceTypeRegisterEvent;
use OCP\OCM\Exceptions\OCMArgumentException;
use OCP\OCM\Exceptions\OCMProviderException;
use OCP\OCM\IOCMProvider;
use OCP\OCM\IOCMResource;
use OCP\Security\Signature\Model\ISignatory;

/**
* @since 28.0.0
Expand All @@ -25,7 +27,7 @@ class OCMProvider implements IOCMProvider {
private string $endPoint = '';
/** @var IOCMResource[] */
private array $resourceTypes = [];

private ?ISignatory $signatory = null;
private bool $emittedEvent = false;

public function __construct(
Expand Down Expand Up @@ -152,6 +154,14 @@ public function extractProtocolEntry(string $resourceName, string $protocol): st
throw new OCMArgumentException('resource not found');
}

public function setSignatory(ISignatory $signatory){
$this->signatory = $signatory;
}

public function getSignatory(): ?ISignatory {
return $this->signatory;
}

/**
* import data from an array
*
Expand All @@ -163,7 +173,7 @@ public function extractProtocolEntry(string $resourceName, string $protocol): st
*/
public function import(array $data): static {
$this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
->setApiVersion((string)($data['apiVersion'] ?? ''))
->setApiVersion((string)($data['version'] ?? ''))
->setEndPoint($data['endPoint'] ?? '');

$resources = [];
Expand All @@ -173,6 +183,13 @@ public function import(array $data): static {
}
$this->setResourceTypes($resources);

$signatory = new Signatory($data['publicKey']['keyId'] ?? '', $data['publicKey']['publicKeyPem'] ?? '');
if ($signatory->getKeyId() !== ''
&& $signatory->getPublicKey() !== '')
{
$this->setSignatory($signatory);
}

if (!$this->looksValid()) {
throw new OCMProviderException('remote provider does not look valid');
}
Expand Down Expand Up @@ -209,8 +226,10 @@ public function jsonSerialize(): array {

return [

Check failure on line 227 in lib/private/OCM/Model/OCMProvider.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidReturnStatement

lib/private/OCM/Model/OCMProvider.php:227:10: InvalidReturnStatement: The inferred type 'array{apiVersion: '1.0-proposal1', enabled: bool, endPoint: string, publicKey: OCP\Security\Signature\Model\ISignatory|null, resourceTypes: list<array{name: string, protocols: array<string, string>, shareTypes: array<array-key, string>}>, version: string}' does not match the declared return type 'array{apiVersion: string, enabled: bool, endPoint: string, resourceTypes: array<array-key, array{name: string, protocols: array<string, string>, shareTypes: array<array-key, string>}>}' for OC\OCM\Model\OCMProvider::jsonSerialize due to additional array shape fields (version, publicKey) (see https://psalm.dev/128)
'enabled' => $this->isEnabled(),
'apiVersion' => $this->getApiVersion(),
'apiVersion' => '1.0-proposal1', // keep it like this to stay compatible with old version
'version' => $this->getApiVersion(),
'endPoint' => $this->getEndPoint(),
'publicKey' => $this->getSignatory(),
'resourceTypes' => $resourceTypes
];
}
Expand Down
34 changes: 1 addition & 33 deletions lib/private/OCM/OCMDiscoveryService.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@
*/
class OCMDiscoveryService implements IOCMDiscoveryService {
private ICache $cache;
private array $supportedAPIVersion =
[
'1.0-proposal1',
'1.0',
'1.1'
];

public function __construct(
ICacheFactory $cacheFactory,
Expand All @@ -56,9 +50,7 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
if (!$skipCache) {
try {
$this->provider->import(json_decode($this->cache->get($remote) ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
if ($this->supportedAPIVersion($this->provider->getApiVersion())) {
return $this->provider; // if cache looks valid, we use it
}
return $this->provider;
} catch (JsonException|OCMProviderException $e) {
// we ignore cache on issues
}
Expand Down Expand Up @@ -91,30 +83,6 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
throw new OCMProviderException('error while requesting remote ocm provider');
}

if (!$this->supportedAPIVersion($this->provider->getApiVersion())) {
throw new OCMProviderException('API version not supported');
}

return $this->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));
}
}
55 changes: 54 additions & 1 deletion lib/private/OCM/OCMSignatoryManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,64 @@

namespace OC\OCM;

use OC\Security\Signature\Model\Signatory;
use OCP\IURLGenerator;
use OCP\Security\PublicPrivateKeyPairs\IKeyPairManager;
use OCP\Security\Signature\Exceptions\SignatoryException;
use OCP\Security\Signature\ISignatoryManager;
use OCP\Security\Signature\Model\IIncomingSignedRequest;
use OCP\Security\Signature\Model\ISignatory;

class OCMSignatoryManager implements ISignatoryManager {

public function generateSignatory(IIncomingSignedRequest $signedRequest): ISignatory {
public function __construct(
private readonly IURLGenerator $urlGenerator,
private readonly IKeyPairManager $keyPairManager,
private readonly OCMDiscoveryService $ocmDiscoveryService,
) {
}


public function getOptions(): array {
return [];
}

public function getLocalSignatory(): ISignatory {
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
$pos = strrpos($url, '/');
if ($pos === false) {
throw new SignatoryException('generated route should contains a slash character');
}

// removing /index.php from url
$path = parse_url($url, PHP_URL_PATH);
if (str_starts_with($path, '/index.php/')) {
$pos = strpos($url, '/index.php');
if ($pos !== false) {
$url = substr_replace($url, '', $pos, 10);
}
}

$keyId = $url . '#signature';
$keyPair = $this->keyPairManager->getKeyPair('core', 'ocm');

return new Signatory($keyId, $keyPair->getPublicKey(), $keyPair->getPrivateKey());
}


public function getRemoteSignatory(IIncomingSignedRequest $signedRequest, bool $retry): ISignatory {

Check failure on line 52 in lib/private/OCM/OCMSignatoryManager.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidNullableReturnType

lib/private/OCM/OCMSignatoryManager.php:52:90: InvalidNullableReturnType: The declared return type 'OCP\Security\Signature\Model\ISignatory' for OC\OCM\OCMSignatoryManager::getRemoteSignatory is not nullable, but 'OCP\Security\Signature\Model\ISignatory|null' contains null (see https://psalm.dev/144)

Check failure

Code scanning / Psalm

InvalidNullableReturnType Error

The declared return type 'OCP\Security\Signature\Model\ISignatory' for OC\OCM\OCMSignatoryManager::getRemoteSignatory is not nullable, but 'OCP\Security\Signature\Model\ISignatory|null' contains null
$data = $signedRequest->getSignatureHeader();
$origKeyId = $data['keyId'];

$parsed = parse_url($origKeyId);
$remote = $parsed['scheme'] . '://' . $parsed['host'];
$ocmProvider = $this->ocmDiscoveryService->discover($remote, $retry);

$signatory = $ocmProvider->getSignatory();
if ($signatory->getKeyId() !== $origKeyId) {
throw new SignatoryException('keyId from provider is different from the one from signed request');
}

return $signatory;

Check failure on line 65 in lib/private/OCM/OCMSignatoryManager.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

NullableReturnStatement

lib/private/OCM/OCMSignatoryManager.php:65:10: NullableReturnStatement: The declared return type 'OCP\Security\Signature\Model\ISignatory' for OC\OCM\OCMSignatoryManager::getRemoteSignatory is not nullable, but the function returns 'OCP\Security\Signature\Model\ISignatory|null' (see https://psalm.dev/139)

Check failure

Code scanning / Psalm

NullableReturnStatement Error

The declared return type 'OCP\Security\Signature\Model\ISignatory' for OC\OCM\OCMSignatoryManager::getRemoteSignatory is not nullable, but the function returns 'OCP\Security\Signature\Model\ISignatory|null'
}
}
Loading

0 comments on commit 58d3efd

Please sign in to comment.