Skip to content

Commit 1c1f488

Browse files
authored
Merge pull request #48451 from nextcloud/bug/noid/federated-addressbook-sync-without-localaddressallowed
fix: make federation address book sync work with allow_local_remote_servers = false
2 parents 4bfa306 + 6be0043 commit 1c1f488

File tree

4 files changed

+103
-15
lines changed

4 files changed

+103
-15
lines changed

apps/dav/lib/CardDAV/SyncService.php

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,25 +108,54 @@ public function ensureSystemAddressBookExists(string $principal, string $uri, ar
108108
}, $this->dbConnection);
109109
}
110110

111+
private function prepareUri(string $host, string $path): string {
112+
/*
113+
* The trailing slash is important for merging the uris together.
114+
*
115+
* $host is stored in oc_trusted_servers.url and usually without a trailing slash.
116+
*
117+
* Example for a report request
118+
*
119+
* $host = 'https://server.internal/cloud'
120+
* $path = 'remote.php/dav/addressbooks/system/system/system'
121+
*
122+
* Without the trailing slash, the webroot is missing:
123+
* https://server.internal/remote.php/dav/addressbooks/system/system/system
124+
*
125+
* Example for a download request
126+
*
127+
* $host = 'https://server.internal/cloud'
128+
* $path = '/cloud/remote.php/dav/addressbooks/system/system/system/Database:alice.vcf'
129+
*
130+
* The response from the remote usually contains the webroot already and must be normalized to:
131+
* https://server.internal/cloud/remote.php/dav/addressbooks/system/system/system/Database:alice.vcf
132+
*/
133+
$host = rtrim($host, '/') . '/';
134+
135+
$uri = \GuzzleHttp\Psr7\UriResolver::resolve(
136+
\GuzzleHttp\Psr7\Utils::uriFor($host),
137+
\GuzzleHttp\Psr7\Utils::uriFor($path)
138+
);
139+
140+
return (string)$uri;
141+
}
142+
111143
/**
112144
* @throws ClientExceptionInterface
113145
*/
114146
protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
115147
$client = $this->clientService->newClient();
116-
117-
// the trailing slash is important for merging base_uri and uri
118-
$url = rtrim($url, '/') . '/';
148+
$uri = $this->prepareUri($url, $addressBookUrl);
119149

120150
$options = [
121151
'auth' => [$userName, $sharedSecret],
122-
'base_uri' => $url,
123152
'body' => $this->buildSyncCollectionRequestBody($syncToken),
124153
'headers' => ['Content-Type' => 'application/xml']
125154
];
126155

127156
$response = $client->request(
128157
'REPORT',
129-
$addressBookUrl,
158+
$uri,
130159
$options
131160
);
132161

@@ -138,17 +167,14 @@ protected function requestSyncReport(string $url, string $userName, string $addr
138167

139168
protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): string {
140169
$client = $this->clientService->newClient();
141-
142-
// the trailing slash is important for merging base_uri and uri
143-
$url = rtrim($url, '/') . '/';
170+
$uri = $this->prepareUri($url, $resourcePath);
144171

145172
$options = [
146173
'auth' => [$userName, $sharedSecret],
147-
'base_uri' => $url,
148174
];
149175

150176
$response = $client->get(
151-
$resourcePath,
177+
$uri,
152178
$options
153179
);
154180

apps/dav/tests/unit/CardDAV/SyncServiceTest.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,4 +426,59 @@ public function testDeleteAddressbookWhenAccessRevoked(): void {
426426
[]
427427
);
428428
}
429+
430+
/**
431+
* @dataProvider providerUseAbsoluteUriReport
432+
*/
433+
public function testUseAbsoluteUriReport(string $host, string $expected): void {
434+
$body = '<?xml version="1.0"?>
435+
<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns">
436+
<d:sync-token>http://sabre.io/ns/sync/1</d:sync-token>
437+
</d:multistatus>';
438+
439+
$requestResponse = new Response(new PsrResponse(
440+
207,
441+
['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)],
442+
$body
443+
));
444+
445+
$this->client
446+
->method('request')
447+
->with(
448+
'REPORT',
449+
$this->callback(function ($uri) use ($expected) {
450+
$this->assertEquals($expected, $uri);
451+
return true;
452+
}),
453+
$this->callback(function ($options) {
454+
$this->assertIsArray($options);
455+
return true;
456+
}),
457+
)
458+
->willReturn($requestResponse);
459+
460+
$this->service->syncRemoteAddressBook(
461+
$host,
462+
'system',
463+
'remote.php/dav/addressbooks/system/system/system',
464+
'1234567890',
465+
null,
466+
'1',
467+
'principals/system/system',
468+
[]
469+
);
470+
}
471+
472+
public function providerUseAbsoluteUriReport(): array {
473+
return [
474+
['https://server.internal', 'https://server.internal/remote.php/dav/addressbooks/system/system/system'],
475+
['https://server.internal/', 'https://server.internal/remote.php/dav/addressbooks/system/system/system'],
476+
['https://server.internal/nextcloud', 'https://server.internal/nextcloud/remote.php/dav/addressbooks/system/system/system'],
477+
['https://server.internal/nextcloud/', 'https://server.internal/nextcloud/remote.php/dav/addressbooks/system/system/system'],
478+
['https://server.internal:8080', 'https://server.internal:8080/remote.php/dav/addressbooks/system/system/system'],
479+
['https://server.internal:8080/', 'https://server.internal:8080/remote.php/dav/addressbooks/system/system/system'],
480+
['https://server.internal:8080/nextcloud', 'https://server.internal:8080/nextcloud/remote.php/dav/addressbooks/system/system/system'],
481+
['https://server.internal:8080/nextcloud/', 'https://server.internal:8080/nextcloud/remote.php/dav/addressbooks/system/system/system'],
482+
];
483+
}
429484
}

lib/private/Http/Client/Client.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,14 +158,15 @@ private function isLocalAddressAllowed(array $options) : bool {
158158
}
159159

160160
protected function preventLocalAddress(string $uri, array $options): void {
161-
if ($this->isLocalAddressAllowed($options)) {
162-
return;
163-
}
164-
165161
$host = parse_url($uri, PHP_URL_HOST);
166162
if ($host === false || $host === null) {
167163
throw new LocalServerException('Could not detect any host');
168164
}
165+
166+
if ($this->isLocalAddressAllowed($options)) {
167+
return;
168+
}
169+
169170
if (!$this->remoteHostValidator->isValid($host)) {
170171
throw new LocalServerException('Host "' . $host . '" violates local access rules');
171172
}

tests/lib/Http/Client/ClientTest.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
130130
], self::invokePrivate($this->client, 'getProxyUri'));
131131
}
132132

133+
public function testPreventLocalAddressThrowOnInvalidUri(): void {
134+
$this->expectException(LocalServerException::class);
135+
$this->expectExceptionMessage('Could not detect any host');
136+
137+
self::invokePrivate($this->client, 'preventLocalAddress', ['!@#$', []]);
138+
}
139+
133140
public function dataPreventLocalAddress():array {
134141
return [
135142
['https://localhost/foo.bar'],
@@ -146,7 +153,6 @@ public function dataPreventLocalAddress():array {
146153
['https://10.0.0.1'],
147154
['https://another-host.local'],
148155
['https://service.localhost'],
149-
['!@#$', true], // test invalid url
150156
['https://normal.host.com'],
151157
['https://com.one-.nextcloud-one.com'],
152158
];

0 commit comments

Comments
 (0)