aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Http/Client
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Http/Client')
-rw-r--r--tests/lib/Http/Client/ClientServiceTest.php80
-rw-r--r--tests/lib/Http/Client/ClientTest.php246
-rw-r--r--tests/lib/Http/Client/DnsPinMiddlewareTest.php547
-rw-r--r--tests/lib/Http/Client/NegativeDnsCacheTest.php29
-rw-r--r--tests/lib/Http/Client/ResponseTest.php16
5 files changed, 753 insertions, 165 deletions
diff --git a/tests/lib/Http/Client/ClientServiceTest.php b/tests/lib/Http/Client/ClientServiceTest.php
index ed1165236aa..fd5b155ca69 100644
--- a/tests/lib/Http/Client/ClientServiceTest.php
+++ b/tests/lib/Http/Client/ClientServiceTest.php
@@ -3,23 +3,26 @@
declare(strict_types=1);
/**
- * Copyright (c) 2015 Lukas Reschke <lukas@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Http\Client;
use GuzzleHttp\Client as GuzzleClient;
-use GuzzleHttp\HandlerStack;
use GuzzleHttp\Handler\CurlHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
use OC\Http\Client\Client;
use OC\Http\Client\ClientService;
use OC\Http\Client\DnsPinMiddleware;
+use OCP\Diagnostics\IEventLogger;
use OCP\ICertificateManager;
use OCP\IConfig;
use OCP\Security\IRemoteHostValidator;
+use Psr\Http\Message\RequestInterface;
+use Psr\Log\LoggerInterface;
/**
* Class ClientServiceTest
@@ -28,26 +31,86 @@ class ClientServiceTest extends \Test\TestCase {
public function testNewClient(): void {
/** @var IConfig $config */
$config = $this->createMock(IConfig::class);
+ $config->method('getSystemValueBool')
+ ->with('dns_pinning', true)
+ ->willReturn(true);
/** @var ICertificateManager $certificateManager */
$certificateManager = $this->createMock(ICertificateManager::class);
$dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
$dnsPinMiddleware
->expects($this->atLeastOnce())
->method('addDnsPinning')
- ->willReturn(function () {
+ ->willReturn(function (): void {
});
$remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
+ $eventLogger = $this->createMock(IEventLogger::class);
+ $logger = $this->createMock(LoggerInterface::class);
$clientService = new ClientService(
$config,
$certificateManager,
$dnsPinMiddleware,
- $remoteHostValidator
+ $remoteHostValidator,
+ $eventLogger,
+ $logger,
);
$handler = new CurlHandler();
$stack = HandlerStack::create($handler);
$stack->push($dnsPinMiddleware->addDnsPinning());
+ $stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
+ $eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
+ }, function () use ($eventLogger): void {
+ $eventLogger->end('http:request');
+ }), 'event logger');
+ $guzzleClient = new GuzzleClient(['handler' => $stack]);
+
+ $this->assertEquals(
+ new Client(
+ $config,
+ $certificateManager,
+ $guzzleClient,
+ $remoteHostValidator,
+ $logger,
+ ),
+ $clientService->newClient()
+ );
+ }
+
+ public function testDisableDnsPinning(): void {
+ /** @var IConfig $config */
+ $config = $this->createMock(IConfig::class);
+ $config->method('getSystemValueBool')
+ ->with('dns_pinning', true)
+ ->willReturn(false);
+ /** @var ICertificateManager $certificateManager */
+ $certificateManager = $this->createMock(ICertificateManager::class);
+ $dnsPinMiddleware = $this->createMock(DnsPinMiddleware::class);
+ $dnsPinMiddleware
+ ->expects($this->never())
+ ->method('addDnsPinning')
+ ->willReturn(function (): void {
+ });
+ $remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
+ $eventLogger = $this->createMock(IEventLogger::class);
+ $logger = $this->createMock(LoggerInterface::class);
+
+ $clientService = new ClientService(
+ $config,
+ $certificateManager,
+ $dnsPinMiddleware,
+ $remoteHostValidator,
+ $eventLogger,
+ $logger,
+ );
+
+ $handler = new CurlHandler();
+ $stack = HandlerStack::create($handler);
+ $stack->push(Middleware::tap(function (RequestInterface $request) use ($eventLogger): void {
+ $eventLogger->start('http:request', $request->getMethod() . ' request to ' . $request->getRequestTarget());
+ }, function () use ($eventLogger): void {
+ $eventLogger->end('http:request');
+ }), 'event logger');
$guzzleClient = new GuzzleClient(['handler' => $stack]);
$this->assertEquals(
@@ -55,7 +118,8 @@ class ClientServiceTest extends \Test\TestCase {
$config,
$certificateManager,
$guzzleClient,
- $remoteHostValidator
+ $remoteHostValidator,
+ $logger,
),
$clientService->newClient()
);
diff --git a/tests/lib/Http/Client/ClientTest.php b/tests/lib/Http/Client/ClientTest.php
index 93948a5daf3..e76b66b52d7 100644
--- a/tests/lib/Http/Client/ClientTest.php
+++ b/tests/lib/Http/Client/ClientTest.php
@@ -3,10 +3,9 @@
declare(strict_types=1);
/**
- * Copyright (c) 2015 Lukas Reschke <lukas@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Http\Client;
@@ -19,6 +18,7 @@ use OCP\ICertificateManager;
use OCP\IConfig;
use OCP\Security\IRemoteHostValidator;
use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
use function parse_url;
/**
@@ -35,6 +35,7 @@ class ClientTest extends \Test\TestCase {
private $config;
/** @var IRemoteHostValidator|MockObject */
private IRemoteHostValidator $remoteHostValidator;
+ private LoggerInterface $logger;
/** @var array */
private $defaultRequestOptions;
@@ -44,32 +45,37 @@ class ClientTest extends \Test\TestCase {
$this->guzzleClient = $this->createMock(\GuzzleHttp\Client::class);
$this->certificateManager = $this->createMock(ICertificateManager::class);
$this->remoteHostValidator = $this->createMock(IRemoteHostValidator::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
$this->client = new Client(
$this->config,
$this->certificateManager,
$this->guzzleClient,
- $this->remoteHostValidator
+ $this->remoteHostValidator,
+ $this->logger,
);
}
public function testGetProxyUri(): void {
$this->config
- ->method('getSystemValue')
- ->with('proxy', null)
- ->willReturn(null);
+ ->method('getSystemValueString')
+ ->with('proxy', '')
+ ->willReturn('');
$this->assertNull(self::invokePrivate($this->client, 'getProxyUri'));
}
public function testGetProxyUriProxyHostEmptyPassword(): void {
- $map = [
- ['proxy', '', 'foo'],
- ['proxyuserpwd', '', null],
- ['proxyexclude', [], []],
- ];
-
$this->config
->method('getSystemValue')
- ->will($this->returnValueMap($map));
+ ->willReturnMap([
+ ['proxyexclude', [], []],
+ ]);
+
+ $this->config
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
$this->assertEquals([
'http' => 'foo',
@@ -79,33 +85,17 @@ class ClientTest extends \Test\TestCase {
public function testGetProxyUriProxyHostWithPassword(): void {
$this->config
- ->expects($this->exactly(3))
+ ->expects($this->once())
->method('getSystemValue')
- ->withConsecutive(
- [
- $this->equalTo('proxy'),
- $this->callback(function ($input) {
- return $input === '';
- })
- ],
- [
- $this->equalTo('proxyuserpwd'),
- $this->callback(function ($input) {
- return $input === '';
- })
- ],
- [
- $this->equalTo('proxyexclude'),
- $this->callback(function ($input) {
- return $input === [];
- })
- ],
- )
- ->willReturnOnConsecutiveCalls(
- 'foo',
- 'username:password',
- [],
- );
+ ->with('proxyexclude', [])
+ ->willReturn([]);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', 'username:password'],
+ ]);
$this->assertEquals([
'http' => 'username:password@foo',
'https' => 'username:password@foo'
@@ -114,33 +104,17 @@ class ClientTest extends \Test\TestCase {
public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
$this->config
- ->expects($this->exactly(3))
+ ->expects($this->once())
->method('getSystemValue')
- ->withConsecutive(
- [
- $this->equalTo('proxy'),
- $this->callback(function ($input) {
- return $input === '';
- })
- ],
- [
- $this->equalTo('proxyuserpwd'),
- $this->callback(function ($input) {
- return $input === '';
- })
- ],
- [
- $this->equalTo('proxyexclude'),
- $this->callback(function ($input) {
- return $input === [];
- })
- ],
- )
- ->willReturnOnConsecutiveCalls(
- 'foo',
- 'username:password',
- ['bar'],
- );
+ ->with('proxyexclude', [])
+ ->willReturn(['bar']);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', 'username:password'],
+ ]);
$this->assertEquals([
'http' => 'username:password@foo',
'https' => 'username:password@foo',
@@ -148,7 +122,14 @@ class ClientTest extends \Test\TestCase {
], self::invokePrivate($this->client, 'getProxyUri'));
}
- public function dataPreventLocalAddress():array {
+ public function testPreventLocalAddressThrowOnInvalidUri(): void {
+ $this->expectException(LocalServerException::class);
+ $this->expectExceptionMessage('Could not detect any host');
+
+ self::invokePrivate($this->client, 'preventLocalAddress', ['!@#$', []]);
+ }
+
+ public static function dataPreventLocalAddress(): array {
return [
['https://localhost/foo.bar'],
['https://localHost/foo.bar'],
@@ -164,15 +145,15 @@ class ClientTest extends \Test\TestCase {
['https://10.0.0.1'],
['https://another-host.local'],
['https://service.localhost'],
- ['!@#$', true], // test invalid url
['https://normal.host.com'],
+ ['https://com.one-.nextcloud-one.com'],
];
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressDisabledByGlobalConfig(string $uri): void {
$this->config->expects($this->once())
->method('getSystemValueBool')
@@ -183,9 +164,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressDisabledByOption(string $uri): void {
$this->config->expects($this->never())
->method('getSystemValueBool');
@@ -196,9 +177,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressOnGet(string $uri): void {
$host = parse_url($uri, PHP_URL_HOST);
$this->expectException(LocalServerException::class);
@@ -211,9 +192,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressOnHead(string $uri): void {
$host = parse_url($uri, PHP_URL_HOST);
$this->expectException(LocalServerException::class);
@@ -226,9 +207,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressOnPost(string $uri): void {
$host = parse_url($uri, PHP_URL_HOST);
$this->expectException(LocalServerException::class);
@@ -241,9 +222,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressOnPut(string $uri): void {
$host = parse_url($uri, PHP_URL_HOST);
$this->expectException(LocalServerException::class);
@@ -256,9 +237,9 @@ class ClientTest extends \Test\TestCase {
}
/**
- * @dataProvider dataPreventLocalAddress
* @param string $uri
*/
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
public function testPreventLocalAddressOnDelete(string $uri): void {
$host = parse_url($uri, PHP_URL_HOST);
$this->expectException(LocalServerException::class);
@@ -271,19 +252,23 @@ class ClientTest extends \Test\TestCase {
}
private function setUpDefaultRequestOptions(): void {
- $map = [
- ['proxy', '', 'foo'],
- ['proxyuserpwd', '', null],
- ['proxyexclude', [], []],
- ];
-
$this->config
->method('getSystemValue')
- ->will($this->returnValueMap($map));
+ ->willReturnMap([
+ ['proxyexclude', [], []],
+ ]);
+ $this->config
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
$this->config
->method('getSystemValueBool')
- ->with('allow_local_remote_servers', false)
- ->willReturn(true);
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, true],
+ ]);
$this->certificateManager
->expects($this->once())
@@ -467,15 +452,16 @@ class ClientTest extends \Test\TestCase {
public function testSetDefaultOptionsWithNotInstalled(): void {
$this->config
->expects($this->exactly(2))
- ->method('getSystemValue')
- ->withConsecutive(
- ['proxy', ''],
- ['installed', false],
- )
- ->willReturnOnConsecutiveCalls(
- '',
- false,
- );
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, false],
+ ['allow_local_remote_servers', false, false],
+ ]);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValueString')
+ ->with('proxy', '')
+ ->willReturn('');
$this->certificateManager
->expects($this->never())
->method('listCertificates');
@@ -494,8 +480,8 @@ class ClientTest extends \Test\TestCase {
'on_redirect' => function (
\Psr\Http\Message\RequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
- \Psr\Http\Message\UriInterface $uri
- ) {
+ \Psr\Http\Message\UriInterface $uri,
+ ): void {
},
],
], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
@@ -503,20 +489,24 @@ class ClientTest extends \Test\TestCase {
public function testSetDefaultOptionsWithProxy(): void {
$this->config
- ->expects($this->exactly(4))
+ ->expects($this->exactly(2))
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, false],
+ ]);
+ $this->config
+ ->expects($this->once())
->method('getSystemValue')
- ->withConsecutive(
- ['proxy', ''],
- ['proxyuserpwd', ''],
- ['proxyexclude', []],
- ['installed', false],
- )
- ->willReturnOnConsecutiveCalls(
- 'foo',
- '',
- [],
- true,
- );
+ ->with('proxyexclude', [])
+ ->willReturn([]);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
$this->certificateManager
->expects($this->once())
->method('getAbsoluteBundlePath')
@@ -541,8 +531,8 @@ class ClientTest extends \Test\TestCase {
'on_redirect' => function (
\Psr\Http\Message\RequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
- \Psr\Http\Message\UriInterface $uri
- ) {
+ \Psr\Http\Message\UriInterface $uri,
+ ): void {
},
],
], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
@@ -550,20 +540,24 @@ class ClientTest extends \Test\TestCase {
public function testSetDefaultOptionsWithProxyAndExclude(): void {
$this->config
- ->expects($this->exactly(4))
+ ->expects($this->exactly(2))
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, false],
+ ]);
+ $this->config
+ ->expects($this->once())
->method('getSystemValue')
- ->withConsecutive(
- ['proxy', ''],
- ['proxyuserpwd', ''],
- ['proxyexclude', []],
- ['installed', false],
- )
- ->willReturnOnConsecutiveCalls(
- 'foo',
- '',
- ['bar'],
- true,
- );
+ ->with('proxyexclude', [])
+ ->willReturn(['bar']);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
$this->certificateManager
->expects($this->once())
->method('getAbsoluteBundlePath')
@@ -589,8 +583,8 @@ class ClientTest extends \Test\TestCase {
'on_redirect' => function (
\Psr\Http\Message\RequestInterface $request,
\Psr\Http\Message\ResponseInterface $response,
- \Psr\Http\Message\UriInterface $uri
- ) {
+ \Psr\Http\Message\UriInterface $uri,
+ ): void {
},
],
], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
diff --git a/tests/lib/Http/Client/DnsPinMiddlewareTest.php b/tests/lib/Http/Client/DnsPinMiddlewareTest.php
new file mode 100644
index 00000000000..9c0aa198cd8
--- /dev/null
+++ b/tests/lib/Http/Client/DnsPinMiddlewareTest.php
@@ -0,0 +1,547 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace lib\Http\Client;
+
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Response;
+use OC\Http\Client\DnsPinMiddleware;
+use OC\Http\Client\NegativeDnsCache;
+use OC\Memcache\NullCache;
+use OC\Net\IpAddressClassifier;
+use OCP\Http\Client\LocalServerException;
+use OCP\ICacheFactory;
+use Psr\Http\Message\RequestInterface;
+use Test\TestCase;
+
+class DnsPinMiddlewareTest extends TestCase {
+ private DnsPinMiddleware $dnsPinMiddleware;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $cacheFactory = $this->createMock(ICacheFactory::class);
+ $cacheFactory
+ ->method('createLocal')
+ ->willReturn(new NullCache());
+
+ $ipAddressClassifier = new IpAddressClassifier();
+ $negativeDnsCache = new NegativeDnsCache($cacheFactory);
+
+ $this->dnsPinMiddleware = $this->getMockBuilder(DnsPinMiddleware::class)
+ ->setConstructorArgs([$negativeDnsCache, $ipAddressClassifier])
+ ->onlyMethods(['dnsGetRecord'])
+ ->getMock();
+ }
+
+ public function testPopulateDnsCacheIPv4(): void {
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options) {
+ self::arrayHasKey('curl', $options);
+ self::arrayHasKey(CURLOPT_RESOLVE, $options['curl']);
+ self::assertEquals([
+ 'www.example.com:80:1.1.1.1',
+ 'www.example.com:443:1.1.1.1'
+ ], $options['curl'][CURLOPT_RESOLVE]);
+ return new Response(200);
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ // example.com SOA
+ if ($hostname === 'example.com') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.com A, AAAA, CNAME
+ if ($hostname === 'www.example.com') {
+ return match ($type) {
+ DNS_A => [],
+ DNS_AAAA => [],
+ DNS_CNAME => [
+ [
+ 'host' => 'www.example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'target' => 'www.example.net'
+ ]
+ ],
+ };
+ }
+
+ // example.net SOA
+ if ($hostname === 'example.net') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.net',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.net A, AAAA, CNAME
+ if ($hostname === 'www.example.net') {
+ return match ($type) {
+ DNS_A => [
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '1.1.1.1'
+ ]
+ ],
+ DNS_AAAA => [],
+ DNS_CNAME => [],
+ };
+ }
+
+ return false;
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testPopulateDnsCacheIPv6(): void {
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options) {
+ self::arrayHasKey('curl', $options);
+ self::arrayHasKey(CURLOPT_RESOLVE, $options['curl']);
+ self::assertEquals([
+ 'www.example.com:80:1.1.1.1,1.0.0.1,2606:4700:4700::1111,2606:4700:4700::1001',
+ 'www.example.com:443:1.1.1.1,1.0.0.1,2606:4700:4700::1111,2606:4700:4700::1001'
+ ], $options['curl'][CURLOPT_RESOLVE]);
+ return new Response(200);
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ // example.com SOA
+ if ($hostname === 'example.com') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.com A, AAAA, CNAME
+ if ($hostname === 'www.example.com') {
+ return match ($type) {
+ DNS_A => [],
+ DNS_AAAA => [],
+ DNS_CNAME => [
+ [
+ 'host' => 'www.example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'target' => 'www.example.net'
+ ]
+ ],
+ };
+ }
+
+ // example.net SOA
+ if ($hostname === 'example.net') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.net',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.net A, AAAA, CNAME
+ if ($hostname === 'www.example.net') {
+ return match ($type) {
+ DNS_A => [
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '1.1.1.1'
+ ],
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '1.0.0.1'
+ ],
+ ],
+ DNS_AAAA => [
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'AAAA',
+ 'ip' => '2606:4700:4700::1111'
+ ],
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'AAAA',
+ 'ip' => '2606:4700:4700::1001'
+ ],
+ ],
+ DNS_CNAME => [],
+ };
+ }
+
+ return false;
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testAllowLocalAddress(): void {
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options) {
+ self::assertArrayNotHasKey('curl', $options);
+ return new Response(200);
+ },
+ ]);
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => true]]
+ );
+ }
+
+ public function testRejectIPv4(): void {
+ $this->expectException(LocalServerException::class);
+ $this->expectExceptionMessage('violates local access rules');
+
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options): void {
+ // The handler should not be called
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ DNS_A => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '192.168.0.1'
+ ]
+ ],
+ DNS_AAAA => [],
+ DNS_CNAME => [],
+ };
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testRejectIPv6(): void {
+ $this->expectException(LocalServerException::class);
+ $this->expectExceptionMessage('violates local access rules');
+
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options): void {
+ // The handler should not be called
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ DNS_A => [],
+ DNS_AAAA => [
+ [
+ 'host' => 'ipv6.example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'AAAA',
+ 'ipv6' => 'fd12:3456:789a:1::1'
+ ]
+ ],
+ DNS_CNAME => [],
+ };
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://ipv6.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testRejectCanonicalName(): void {
+ $this->expectException(LocalServerException::class);
+ $this->expectExceptionMessage('violates local access rules');
+
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options): void {
+ // The handler should not be called
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ // example.com SOA
+ if ($hostname === 'example.com') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.com A, AAAA, CNAME
+ if ($hostname === 'www.example.com') {
+ return match ($type) {
+ DNS_A => [],
+ DNS_AAAA => [],
+ DNS_CNAME => [
+ [
+ 'host' => 'www.example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'target' => 'www.example.net'
+ ]
+ ],
+ };
+ }
+
+ // example.net SOA
+ if ($hostname === 'example.net') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.net',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.net A, AAAA, CNAME
+ if ($hostname === 'www.example.net') {
+ return match ($type) {
+ DNS_A => [
+ [
+ 'host' => 'www.example.net',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '192.168.0.2'
+ ]
+ ],
+ DNS_AAAA => [],
+ DNS_CNAME => [],
+ };
+ }
+
+ return false;
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testRejectFaultyResponse(): void {
+ $this->expectException(LocalServerException::class);
+ $this->expectExceptionMessage('No DNS record found for www.example.com');
+
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options): void {
+ // The handler should not be called
+ },
+ ]);
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) {
+ return false;
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://www.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+ }
+
+ public function testIgnoreSubdomainForSoaQuery(): void {
+ $mockHandler = new MockHandler([
+ static function (RequestInterface $request, array $options): void {
+ // The handler should not be called
+ },
+ ]);
+
+ $dnsQueries = [];
+
+ $this->dnsPinMiddleware
+ ->method('dnsGetRecord')
+ ->willReturnCallback(function (string $hostname, int $type) use (&$dnsQueries) {
+ // log query
+ $dnsQueries[] = $hostname . $type;
+
+ // example.com SOA
+ if ($hostname === 'example.com') {
+ return match ($type) {
+ DNS_SOA => [
+ [
+ 'host' => 'example.com',
+ 'class' => 'IN',
+ 'ttl' => 7079,
+ 'type' => 'SOA',
+ 'minimum-ttl' => 3600,
+ ]
+ ],
+ };
+ }
+
+ // example.net A, AAAA, CNAME
+ if ($hostname === 'subsubdomain.subdomain.example.com') {
+ return match ($type) {
+ DNS_A => [
+ [
+ 'host' => 'subsubdomain.subdomain.example.com',
+ 'class' => 'IN',
+ 'ttl' => 1800,
+ 'type' => 'A',
+ 'ip' => '1.1.1.1'
+ ]
+ ],
+ DNS_AAAA => [],
+ DNS_CNAME => [],
+ };
+ }
+
+ return false;
+ });
+
+ $stack = new HandlerStack($mockHandler);
+ $stack->push($this->dnsPinMiddleware->addDnsPinning());
+ $handler = $stack->resolve();
+
+ $handler(
+ new Request('GET', 'https://subsubdomain.subdomain.example.com'),
+ ['nextcloud' => ['allow_local_address' => false]]
+ );
+
+ $this->assertCount(3, $dnsQueries);
+ $this->assertContains('example.com' . DNS_SOA, $dnsQueries);
+ $this->assertContains('subsubdomain.subdomain.example.com' . DNS_A, $dnsQueries);
+ $this->assertContains('subsubdomain.subdomain.example.com' . DNS_AAAA, $dnsQueries);
+ // CNAME should not be queried if A or AAAA succeeded already
+ $this->assertNotContains('subsubdomain.subdomain.example.com' . DNS_CNAME, $dnsQueries);
+ }
+}
diff --git a/tests/lib/Http/Client/NegativeDnsCacheTest.php b/tests/lib/Http/Client/NegativeDnsCacheTest.php
index b36524acf90..eb0f86f5c7a 100644
--- a/tests/lib/Http/Client/NegativeDnsCacheTest.php
+++ b/tests/lib/Http/Client/NegativeDnsCacheTest.php
@@ -3,25 +3,8 @@
declare(strict_types=1);
/**
- * @copyright Copyright (c) 2021, Lukas Reschke <lukas@statuscode.ch>
- *
- * @author Lukas Reschke <lukas@statuscode.ch>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Http\Client;
@@ -47,7 +30,7 @@ class NegativeDnsCacheTest extends \Test\TestCase {
->method('createLocal')
->with('NegativeDnsCache')
->willReturn($this->cache);
-
+
$this->negativeDnsCache = new NegativeDnsCache($this->cacheFactory);
}
@@ -57,16 +40,16 @@ class NegativeDnsCacheTest extends \Test\TestCase {
->method('set')
->with('www.example.com-1', 'true', 3600);
- $this->negativeDnsCache->setNegativeCacheForDnsType("www.example.com", DNS_A, 3600);
+ $this->negativeDnsCache->setNegativeCacheForDnsType('www.example.com', DNS_A, 3600);
}
- public function testIsNegativeCached() {
+ public function testIsNegativeCached(): void {
$this->cache
->expects($this->once())
->method('hasKey')
->with('www.example.com-1')
->willReturn(true);
- $this->assertTrue($this->negativeDnsCache->isNegativeCached("www.example.com", DNS_A));
+ $this->assertTrue($this->negativeDnsCache->isNegativeCached('www.example.com', DNS_A));
}
}
diff --git a/tests/lib/Http/Client/ResponseTest.php b/tests/lib/Http/Client/ResponseTest.php
index 1384e4e732c..1acf1eb1cbd 100644
--- a/tests/lib/Http/Client/ResponseTest.php
+++ b/tests/lib/Http/Client/ResponseTest.php
@@ -1,9 +1,9 @@
<?php
+
/**
- * Copyright (c) 2015 Lukas Reschke <lukas@owncloud.com>
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
+ * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Http\Client;
@@ -24,22 +24,22 @@ class ResponseTest extends \Test\TestCase {
$this->guzzleResponse = new GuzzleResponse(418);
}
- public function testGetBody() {
+ public function testGetBody(): void {
$response = new Response($this->guzzleResponse->withBody(Utils::streamFor('MyResponse')));
$this->assertSame('MyResponse', $response->getBody());
}
- public function testGetStatusCode() {
+ public function testGetStatusCode(): void {
$response = new Response($this->guzzleResponse);
$this->assertSame(418, $response->getStatusCode());
}
- public function testGetHeader() {
+ public function testGetHeader(): void {
$response = new Response($this->guzzleResponse->withHeader('bar', 'foo'));
$this->assertSame('foo', $response->getHeader('bar'));
}
- public function testGetHeaders() {
+ public function testGetHeaders(): void {
$response = new Response($this->guzzleResponse
->withHeader('bar', 'foo')
->withHeader('x-awesome', 'yes')