Skip to content

Commit 58d3efd

Browse files
committed
outgoing request
Signed-off-by: Maxence Lange <[email protected]>
1 parent a6481e3 commit 58d3efd

File tree

22 files changed

+385
-123
lines changed

22 files changed

+385
-123
lines changed

apps/cloud_federation_api/lib/Capabilities.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@
99

1010
namespace OCA\CloudFederationAPI;
1111

12+
use OC\OCM\OCMSignatoryManager;
13+
use OC\Security\Signature\Model\Signatory;
1214
use OCP\Capabilities\ICapability;
1315
use OCP\IURLGenerator;
1416
use OCP\OCM\Exceptions\OCMArgumentException;
1517
use OCP\OCM\IOCMProvider;
18+
use OCP\Security\Signature\Exceptions\SignatoryException;
19+
use OCP\Security\Signature\Model\ISignatory;
20+
use Psr\Log\LoggerInterface;
1621

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

2025
public function __construct(
2126
private IURLGenerator $urlGenerator,
2227
private IOCMProvider $provider,
28+
private readonly OCMSignatoryManager $ocmSignatoryManager,
29+
private readonly LoggerInterface $logger,
2330
) {
2431
}
2532

@@ -60,6 +67,12 @@ public function getCapabilities() {
6067

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

70+
try {
71+
$this->provider->setSignatory($this->ocmSignatoryManager->getLocalSignatory());
72+
} catch (SignatoryException $e) {
73+
$this->logger->warning('cannot generate local signatory', ['exception' => $e]);
74+
}
75+
6376
return ['ocm' => $this->provider->jsonSerialize()];
6477
}
6578
}

apps/cloud_federation_api/lib/Controller/RequestHandlerController.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace OCA\CloudFederationAPI\Controller;
77

8+
use OC\OCM\OCMSignatoryManager;
89
use OCA\CloudFederationAPI\Config;
910
use OCA\CloudFederationAPI\ResponseDefinitions;
1011
use OCP\AppFramework\Controller;
@@ -23,6 +24,8 @@
2324
use OCP\IRequest;
2425
use OCP\IURLGenerator;
2526
use OCP\IUserManager;
27+
use OCP\Security\Signature\Exceptions\SignatureException;
28+
use OCP\Security\Signature\ISignatureManager;
2629
use OCP\Share\Exceptions\ShareNotFound;
2730
use Psr\Log\LoggerInterface;
2831

@@ -47,7 +50,9 @@ public function __construct(
4750
private ICloudFederationProviderManager $cloudFederationProviderManager,
4851
private Config $config,
4952
private ICloudFederationFactory $factory,
50-
private ICloudIdManager $cloudIdManager
53+
private ICloudIdManager $cloudIdManager,
54+
private readonly ISignatureManager $signatureManager,
55+
private readonly OCMSignatoryManager $signatoryManager,
5156
) {
5257
parent::__construct($appName, $request);
5358
}
@@ -77,6 +82,12 @@ public function __construct(
7782
* 501: Share type or the resource type is not supported
7883
*/
7984
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
85+
try {
86+
$signedRequest = $this->signatureManager->getIncomingSignedRequest($this->signatoryManager);
87+
} catch (SignatureException $e) {
88+
// impossible to confirm signature
89+
}
90+
8091
// check if all required parameters are set
8192
if ($shareWith === null ||
8293
$name === null ||
@@ -99,6 +110,7 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
99110
);
100111
}
101112

113+
102114
$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
103115
if (!in_array($shareType, $supportedShareTypes)) {
104116
return new JSONResponse(

apps/files_sharing/lib/External/Cache.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ class Cache extends \OC\Files\Cache\Cache {
2222
public function __construct($storage, ICloudId $cloudId) {
2323
$this->cloudId = $cloudId;
2424
$this->storage = $storage;
25-
[, $remote] = explode('://', $cloudId->getRemote(), 2);
25+
$remote = $cloudId->getRemote();
26+
if (str_contains($remote, '://')) {
27+
[, $remote] = explode('://', $cloudId->getRemote(), 2);
28+
}
2629
$this->remote = $remote;
2730
$this->remoteUser = $cloudId->getUser();
2831
parent::__construct($storage);

core/Controller/OCMController.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
1616
use OCP\AppFramework\Http\DataResponse;
1717
use OCP\Capabilities\ICapability;
18+
use OCP\IAppConfig;
1819
use OCP\IConfig;
1920
use OCP\IRequest;
2021
use OCP\Server;
@@ -29,7 +30,7 @@
2930
class OCMController extends Controller {
3031
public function __construct(
3132
IRequest $request,
32-
private IConfig $config,
33+
private readonly IAppConfig $appConfig,
3334
private LoggerInterface $logger
3435
) {
3536
parent::__construct('core', $request);
@@ -52,10 +53,10 @@ public function __construct(
5253
public function discovery(): DataResponse {
5354
try {
5455
$cap = Server::get(
55-
$this->config->getAppValue(
56-
'core',
57-
'ocm_providers',
58-
'\OCA\CloudFederationAPI\Capabilities'
56+
$this->appConfig->getValueString(
57+
'core', 'ocm_providers',
58+
\OCA\CloudFederationAPI\Capabilities::class,
59+
lazy: true
5960
)
6061
);
6162

lib/private/AppFramework/Http/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ public function getCookie(string $key) {
352352
*
353353
* @throws \LogicException
354354
*/
355-
protected function getContent() {
355+
public function getContent() {
356356
// If the content can't be parsed into an array then return a stream resource.
357357
if ($this->isPutStreamContent()) {
358358
if ($this->content === false) {

lib/private/Federation/CloudFederationProviderManager.php

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace OC\Federation;
1010

1111
use OC\AppFramework\Http;
12+
use OC\OCM\OCMSignatoryManager;
1213
use OCP\App\IAppManager;
1314
use OCP\Federation\Exceptions\ProviderDoesNotExistsException;
1415
use OCP\Federation\ICloudFederationNotification;
@@ -21,6 +22,7 @@
2122
use OCP\IConfig;
2223
use OCP\OCM\Exceptions\OCMProviderException;
2324
use OCP\OCM\IOCMDiscoveryService;
25+
use OCP\Security\Signature\ISignatureManager;
2426
use Psr\Log\LoggerInterface;
2527

2628
/**
@@ -40,7 +42,9 @@ public function __construct(
4042
private IClientService $httpClientService,
4143
private ICloudIdManager $cloudIdManager,
4244
private IOCMDiscoveryService $discoveryService,
43-
private LoggerInterface $logger
45+
private readonly ISignatureManager $signatureManager,
46+
private readonly OCMSignatoryManager $signatoryManager,
47+
private LoggerInterface $logger,
4448
) {
4549
}
4650

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

107111
$client = $this->httpClientService->newClient();
108112
try {
109-
$response = $client->post($ocmProvider->getEndPoint() . '/shares', [
110-
'body' => json_encode($share->getShare()),
111-
'headers' => ['content-type' => 'application/json'],
112-
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
113-
'timeout' => 10,
114-
'connect_timeout' => 10,
115-
]);
113+
$uri = $ocmProvider->getEndPoint() . '/shares';
114+
$signedPayload = $this->signatureManager->signOutgoingRequestIClientPayload(
115+
$this->signatoryManager,
116+
[
117+
'body' => json_encode($share->getShare()),
118+
'headers' => ['content-type' => 'application/json'],
119+
'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false),
120+
'timeout' => 10,
121+
'connect_timeout' => 10,
122+
],
123+
'post', $uri);
124+
$response = $client->post($uri, $signedPayload);
116125

117126
if ($response->getStatusCode() === Http::STATUS_CREATED) {
118127
$result = json_decode($response->getBody(), true);

lib/private/OCM/Model/OCMProvider.php

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99

1010
namespace OC\OCM\Model;
1111

12+
use OC\Security\Signature\Model\Signatory;
1213
use OCP\EventDispatcher\IEventDispatcher;
1314
use OCP\OCM\Events\ResourceTypeRegisterEvent;
1415
use OCP\OCM\Exceptions\OCMArgumentException;
1516
use OCP\OCM\Exceptions\OCMProviderException;
1617
use OCP\OCM\IOCMProvider;
1718
use OCP\OCM\IOCMResource;
19+
use OCP\Security\Signature\Model\ISignatory;
1820

1921
/**
2022
* @since 28.0.0
@@ -25,7 +27,7 @@ class OCMProvider implements IOCMProvider {
2527
private string $endPoint = '';
2628
/** @var IOCMResource[] */
2729
private array $resourceTypes = [];
28-
30+
private ?ISignatory $signatory = null;
2931
private bool $emittedEvent = false;
3032

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

157+
public function setSignatory(ISignatory $signatory){
158+
$this->signatory = $signatory;
159+
}
160+
161+
public function getSignatory(): ?ISignatory {
162+
return $this->signatory;
163+
}
164+
155165
/**
156166
* import data from an array
157167
*
@@ -163,7 +173,7 @@ public function extractProtocolEntry(string $resourceName, string $protocol): st
163173
*/
164174
public function import(array $data): static {
165175
$this->setEnabled(is_bool($data['enabled'] ?? '') ? $data['enabled'] : false)
166-
->setApiVersion((string)($data['apiVersion'] ?? ''))
176+
->setApiVersion((string)($data['version'] ?? ''))
167177
->setEndPoint($data['endPoint'] ?? '');
168178

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

186+
$signatory = new Signatory($data['publicKey']['keyId'] ?? '', $data['publicKey']['publicKeyPem'] ?? '');
187+
if ($signatory->getKeyId() !== ''
188+
&& $signatory->getPublicKey() !== '')
189+
{
190+
$this->setSignatory($signatory);
191+
}
192+
176193
if (!$this->looksValid()) {
177194
throw new OCMProviderException('remote provider does not look valid');
178195
}
@@ -209,8 +226,10 @@ public function jsonSerialize(): array {
209226

210227
return [
211228
'enabled' => $this->isEnabled(),
212-
'apiVersion' => $this->getApiVersion(),
229+
'apiVersion' => '1.0-proposal1', // keep it like this to stay compatible with old version
230+
'version' => $this->getApiVersion(),
213231
'endPoint' => $this->getEndPoint(),
232+
'publicKey' => $this->getSignatory(),
214233
'resourceTypes' => $resourceTypes
215234
];
216235
}

lib/private/OCM/OCMDiscoveryService.php

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@
2525
*/
2626
class OCMDiscoveryService implements IOCMDiscoveryService {
2727
private ICache $cache;
28-
private array $supportedAPIVersion =
29-
[
30-
'1.0-proposal1',
31-
'1.0',
32-
'1.1'
33-
];
3428

3529
public function __construct(
3630
ICacheFactory $cacheFactory,
@@ -56,9 +50,7 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
5650
if (!$skipCache) {
5751
try {
5852
$this->provider->import(json_decode($this->cache->get($remote) ?? '', true, 8, JSON_THROW_ON_ERROR) ?? []);
59-
if ($this->supportedAPIVersion($this->provider->getApiVersion())) {
60-
return $this->provider; // if cache looks valid, we use it
61-
}
53+
return $this->provider;
6254
} catch (JsonException|OCMProviderException $e) {
6355
// we ignore cache on issues
6456
}
@@ -91,30 +83,6 @@ public function discover(string $remote, bool $skipCache = false): IOCMProvider
9183
throw new OCMProviderException('error while requesting remote ocm provider');
9284
}
9385

94-
if (!$this->supportedAPIVersion($this->provider->getApiVersion())) {
95-
throw new OCMProviderException('API version not supported');
96-
}
97-
9886
return $this->provider;
9987
}
100-
101-
/**
102-
* Check the version from remote is supported.
103-
* The minor version of the API will be ignored:
104-
* 1.0.1 is identified as 1.0
105-
*
106-
* @param string $version
107-
*
108-
* @return bool
109-
*/
110-
private function supportedAPIVersion(string $version): bool {
111-
$dot1 = strpos($version, '.');
112-
$dot2 = strpos($version, '.', $dot1 + 1);
113-
114-
if ($dot2 > 0) {
115-
$version = substr($version, 0, $dot2);
116-
}
117-
118-
return (in_array($version, $this->supportedAPIVersion));
119-
}
12088
}

lib/private/OCM/OCMSignatoryManager.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,64 @@
44

55
namespace OC\OCM;
66

7+
use OC\Security\Signature\Model\Signatory;
8+
use OCP\IURLGenerator;
9+
use OCP\Security\PublicPrivateKeyPairs\IKeyPairManager;
10+
use OCP\Security\Signature\Exceptions\SignatoryException;
711
use OCP\Security\Signature\ISignatoryManager;
12+
use OCP\Security\Signature\Model\IIncomingSignedRequest;
13+
use OCP\Security\Signature\Model\ISignatory;
814

915
class OCMSignatoryManager implements ISignatoryManager {
1016

11-
public function generateSignatory(IIncomingSignedRequest $signedRequest): ISignatory {
17+
public function __construct(
18+
private readonly IURLGenerator $urlGenerator,
19+
private readonly IKeyPairManager $keyPairManager,
20+
private readonly OCMDiscoveryService $ocmDiscoveryService,
21+
) {
22+
}
23+
24+
25+
public function getOptions(): array {
26+
return [];
27+
}
28+
29+
public function getLocalSignatory(): ISignatory {
30+
$url = $this->urlGenerator->linkToRouteAbsolute('cloud_federation_api.requesthandlercontroller.addShare');
31+
$pos = strrpos($url, '/');
32+
if ($pos === false) {
33+
throw new SignatoryException('generated route should contains a slash character');
34+
}
35+
36+
// removing /index.php from url
37+
$path = parse_url($url, PHP_URL_PATH);
38+
if (str_starts_with($path, '/index.php/')) {
39+
$pos = strpos($url, '/index.php');
40+
if ($pos !== false) {
41+
$url = substr_replace($url, '', $pos, 10);
42+
}
43+
}
44+
45+
$keyId = $url . '#signature';
46+
$keyPair = $this->keyPairManager->getKeyPair('core', 'ocm');
47+
48+
return new Signatory($keyId, $keyPair->getPublicKey(), $keyPair->getPrivateKey());
49+
}
50+
51+
52+
public function getRemoteSignatory(IIncomingSignedRequest $signedRequest, bool $retry): ISignatory {
53+
$data = $signedRequest->getSignatureHeader();
54+
$origKeyId = $data['keyId'];
55+
56+
$parsed = parse_url($origKeyId);
57+
$remote = $parsed['scheme'] . '://' . $parsed['host'];
58+
$ocmProvider = $this->ocmDiscoveryService->discover($remote, $retry);
59+
60+
$signatory = $ocmProvider->getSignatory();
61+
if ($signatory->getKeyId() !== $origKeyId) {
62+
throw new SignatoryException('keyId from provider is different from the one from signed request');
63+
}
1264

65+
return $signatory;
1366
}
1467
}

0 commit comments

Comments
 (0)