aboutsummaryrefslogtreecommitdiffstats
path: root/tests/lib/Http
diff options
context:
space:
mode:
Diffstat (limited to 'tests/lib/Http')
-rw-r--r--tests/lib/Http/Client/ClientServiceTest.php127
-rw-r--r--tests/lib/Http/Client/ClientTest.php592
-rw-r--r--tests/lib/Http/Client/DnsPinMiddlewareTest.php547
-rw-r--r--tests/lib/Http/Client/NegativeDnsCacheTest.php55
-rw-r--r--tests/lib/Http/Client/ResponseTest.php59
-rw-r--r--tests/lib/Http/WellKnown/GenericResponseTest.php24
-rw-r--r--tests/lib/Http/WellKnown/JrdResponseTest.php92
-rw-r--r--tests/lib/Http/WellKnown/RequestManagerTest.php154
8 files changed, 1650 insertions, 0 deletions
diff --git a/tests/lib/Http/Client/ClientServiceTest.php b/tests/lib/Http/Client/ClientServiceTest.php
new file mode 100644
index 00000000000..fd5b155ca69
--- /dev/null
+++ b/tests/lib/Http/Client/ClientServiceTest.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * 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\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
+ */
+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 (): 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($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(
+ new Client(
+ $config,
+ $certificateManager,
+ $guzzleClient,
+ $remoteHostValidator,
+ $logger,
+ ),
+ $clientService->newClient()
+ );
+ }
+}
diff --git a/tests/lib/Http/Client/ClientTest.php b/tests/lib/Http/Client/ClientTest.php
new file mode 100644
index 00000000000..e76b66b52d7
--- /dev/null
+++ b/tests/lib/Http/Client/ClientTest.php
@@ -0,0 +1,592 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * 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\Psr7\Response;
+use OC\Http\Client\Client;
+use OC\Security\CertificateManager;
+use OCP\Http\Client\LocalServerException;
+use OCP\ICertificateManager;
+use OCP\IConfig;
+use OCP\Security\IRemoteHostValidator;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use function parse_url;
+
+/**
+ * Class ClientTest
+ */
+class ClientTest extends \Test\TestCase {
+ /** @var \GuzzleHttp\Client|MockObject */
+ private $guzzleClient;
+ /** @var CertificateManager|MockObject */
+ private $certificateManager;
+ /** @var Client */
+ private $client;
+ /** @var IConfig|MockObject */
+ private $config;
+ /** @var IRemoteHostValidator|MockObject */
+ private IRemoteHostValidator $remoteHostValidator;
+ private LoggerInterface $logger;
+ /** @var array */
+ private $defaultRequestOptions;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->config = $this->createMock(IConfig::class);
+ $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->logger,
+ );
+ }
+
+ public function testGetProxyUri(): void {
+ $this->config
+ ->method('getSystemValueString')
+ ->with('proxy', '')
+ ->willReturn('');
+ $this->assertNull(self::invokePrivate($this->client, 'getProxyUri'));
+ }
+
+ public function testGetProxyUriProxyHostEmptyPassword(): void {
+ $this->config
+ ->method('getSystemValue')
+ ->willReturnMap([
+ ['proxyexclude', [], []],
+ ]);
+
+ $this->config
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
+
+ $this->assertEquals([
+ 'http' => 'foo',
+ 'https' => 'foo'
+ ], self::invokePrivate($this->client, 'getProxyUri'));
+ }
+
+ public function testGetProxyUriProxyHostWithPassword(): void {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->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'
+ ], self::invokePrivate($this->client, 'getProxyUri'));
+ }
+
+ public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->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',
+ 'no' => ['bar']
+ ], self::invokePrivate($this->client, 'getProxyUri'));
+ }
+
+ 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'],
+ ['https://random-host/foo.bar'],
+ ['https://[::1]/bla.blub'],
+ ['https://[::]/bla.blub'],
+ ['https://192.168.0.1'],
+ ['https://172.16.42.1'],
+ ['https://[fdf8:f53b:82e4::53]/secret.ics'],
+ ['https://[fe80::200:5aee:feaa:20a2]/secret.ics'],
+ ['https://[0:0:0:0:0:0:10.0.0.1]/secret.ics'],
+ ['https://[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'],
+ ['https://10.0.0.1'],
+ ['https://another-host.local'],
+ ['https://service.localhost'],
+ ['https://normal.host.com'],
+ ['https://com.one-.nextcloud-one.com'],
+ ];
+ }
+
+ /**
+ * @param string $uri
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
+ public function testPreventLocalAddressDisabledByGlobalConfig(string $uri): void {
+ $this->config->expects($this->once())
+ ->method('getSystemValueBool')
+ ->with('allow_local_remote_servers', false)
+ ->willReturn(true);
+
+ self::invokePrivate($this->client, 'preventLocalAddress', [$uri, []]);
+ }
+
+ /**
+ * @param string $uri
+ */
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataPreventLocalAddress')]
+ public function testPreventLocalAddressDisabledByOption(string $uri): void {
+ $this->config->expects($this->never())
+ ->method('getSystemValueBool');
+
+ self::invokePrivate($this->client, 'preventLocalAddress', [$uri, [
+ 'nextcloud' => ['allow_local_address' => true],
+ ]]);
+ }
+
+ /**
+ * @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);
+ $this->remoteHostValidator
+ ->method('isValid')
+ ->with($host)
+ ->willReturn(false);
+
+ $this->client->get($uri);
+ }
+
+ /**
+ * @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);
+ $this->remoteHostValidator
+ ->method('isValid')
+ ->with($host)
+ ->willReturn(false);
+
+ $this->client->head($uri);
+ }
+
+ /**
+ * @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);
+ $this->remoteHostValidator
+ ->method('isValid')
+ ->with($host)
+ ->willReturn(false);
+
+ $this->client->post($uri);
+ }
+
+ /**
+ * @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);
+ $this->remoteHostValidator
+ ->method('isValid')
+ ->with($host)
+ ->willReturn(false);
+
+ $this->client->put($uri);
+ }
+
+ /**
+ * @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);
+ $this->remoteHostValidator
+ ->method('isValid')
+ ->with($host)
+ ->willReturn(false);
+
+ $this->client->delete($uri);
+ }
+
+ private function setUpDefaultRequestOptions(): void {
+ $this->config
+ ->method('getSystemValue')
+ ->willReturnMap([
+ ['proxyexclude', [], []],
+ ]);
+ $this->config
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
+ $this->config
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, true],
+ ]);
+
+ $this->certificateManager
+ ->expects($this->once())
+ ->method('getAbsoluteBundlePath')
+ ->with()
+ ->willReturn('/my/path.crt');
+
+ $this->defaultRequestOptions = [
+ 'verify' => '/my/path.crt',
+ 'proxy' => [
+ 'http' => 'foo',
+ 'https' => 'foo'
+ ],
+ 'headers' => [
+ 'User-Agent' => 'Nextcloud Server Crawler',
+ 'Accept-Encoding' => 'gzip',
+ ],
+ 'timeout' => 30,
+ 'nextcloud' => [
+ 'allow_local_address' => true,
+ ],
+ ];
+ }
+
+ public function testGet(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('get', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->get('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testGetWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('get', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->get('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testPost(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('post', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->post('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testPostWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('post', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->post('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testPut(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('put', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->put('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testPutWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('put', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->put('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testDelete(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('delete', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->delete('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testDeleteWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('delete', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->delete('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('options', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->options('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testOptionsWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('options', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->options('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testHead(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $this->guzzleClient->method('request')
+ ->with('head', 'http://localhost/', $this->defaultRequestOptions)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->head('http://localhost/', [])->getStatusCode());
+ }
+
+ public function testHeadWithOptions(): void {
+ $this->setUpDefaultRequestOptions();
+
+ $options = array_merge($this->defaultRequestOptions, [
+ 'verify' => false,
+ 'proxy' => [
+ 'http' => 'bar',
+ 'https' => 'bar'
+ ],
+ ]);
+
+ $this->guzzleClient->method('request')
+ ->with('head', 'http://localhost/', $options)
+ ->willReturn(new Response(418));
+ $this->assertEquals(418, $this->client->head('http://localhost/', $options)->getStatusCode());
+ }
+
+ public function testSetDefaultOptionsWithNotInstalled(): void {
+ $this->config
+ ->expects($this->exactly(2))
+ ->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');
+
+ $this->assertEquals([
+ 'verify' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',
+ 'headers' => [
+ 'User-Agent' => 'Nextcloud Server Crawler',
+ 'Accept-Encoding' => 'gzip',
+ ],
+ 'timeout' => 30,
+ 'nextcloud' => [
+ 'allow_local_address' => false,
+ ],
+ 'allow_redirects' => [
+ 'on_redirect' => function (
+ \Psr\Http\Message\RequestInterface $request,
+ \Psr\Http\Message\ResponseInterface $response,
+ \Psr\Http\Message\UriInterface $uri,
+ ): void {
+ },
+ ],
+ ], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
+ }
+
+ public function testSetDefaultOptionsWithProxy(): void {
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, false],
+ ]);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('proxyexclude', [])
+ ->willReturn([]);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
+ $this->certificateManager
+ ->expects($this->once())
+ ->method('getAbsoluteBundlePath')
+ ->with()
+ ->willReturn('/my/path.crt');
+
+ $this->assertEquals([
+ 'verify' => '/my/path.crt',
+ 'proxy' => [
+ 'http' => 'foo',
+ 'https' => 'foo'
+ ],
+ 'headers' => [
+ 'User-Agent' => 'Nextcloud Server Crawler',
+ 'Accept-Encoding' => 'gzip',
+ ],
+ 'timeout' => 30,
+ 'nextcloud' => [
+ 'allow_local_address' => false,
+ ],
+ 'allow_redirects' => [
+ 'on_redirect' => function (
+ \Psr\Http\Message\RequestInterface $request,
+ \Psr\Http\Message\ResponseInterface $response,
+ \Psr\Http\Message\UriInterface $uri,
+ ): void {
+ },
+ ],
+ ], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
+ }
+
+ public function testSetDefaultOptionsWithProxyAndExclude(): void {
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueBool')
+ ->willReturnMap([
+ ['installed', false, true],
+ ['allow_local_remote_servers', false, false],
+ ]);
+ $this->config
+ ->expects($this->once())
+ ->method('getSystemValue')
+ ->with('proxyexclude', [])
+ ->willReturn(['bar']);
+ $this->config
+ ->expects($this->exactly(2))
+ ->method('getSystemValueString')
+ ->willReturnMap([
+ ['proxy', '', 'foo'],
+ ['proxyuserpwd', '', ''],
+ ]);
+ $this->certificateManager
+ ->expects($this->once())
+ ->method('getAbsoluteBundlePath')
+ ->with()
+ ->willReturn('/my/path.crt');
+
+ $this->assertEquals([
+ 'verify' => '/my/path.crt',
+ 'proxy' => [
+ 'http' => 'foo',
+ 'https' => 'foo',
+ 'no' => ['bar']
+ ],
+ 'headers' => [
+ 'User-Agent' => 'Nextcloud Server Crawler',
+ 'Accept-Encoding' => 'gzip',
+ ],
+ 'timeout' => 30,
+ 'nextcloud' => [
+ 'allow_local_address' => false,
+ ],
+ 'allow_redirects' => [
+ 'on_redirect' => function (
+ \Psr\Http\Message\RequestInterface $request,
+ \Psr\Http\Message\ResponseInterface $response,
+ \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
new file mode 100644
index 00000000000..eb0f86f5c7a
--- /dev/null
+++ b/tests/lib/Http/Client/NegativeDnsCacheTest.php
@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Http\Client;
+
+use OC\Http\Client\NegativeDnsCache;
+use OCP\ICache;
+use OCP\ICacheFactory;
+
+class NegativeDnsCacheTest extends \Test\TestCase {
+ /** @var ICache */
+ private $cache;
+ /** @var ICacheFactory */
+ private $cacheFactory;
+ /** @var NegativeDnsCache */
+ private $negativeDnsCache;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->cache = $this->createMock(ICache::class);
+ $this->cacheFactory = $this->createMock(ICacheFactory::class);
+ $this->cacheFactory
+ ->method('createLocal')
+ ->with('NegativeDnsCache')
+ ->willReturn($this->cache);
+
+ $this->negativeDnsCache = new NegativeDnsCache($this->cacheFactory);
+ }
+
+ public function testSetNegativeCacheForDnsType() : void {
+ $this->cache
+ ->expects($this->once())
+ ->method('set')
+ ->with('www.example.com-1', 'true', 3600);
+
+ $this->negativeDnsCache->setNegativeCacheForDnsType('www.example.com', DNS_A, 3600);
+ }
+
+ 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));
+ }
+}
diff --git a/tests/lib/Http/Client/ResponseTest.php b/tests/lib/Http/Client/ResponseTest.php
new file mode 100644
index 00000000000..1acf1eb1cbd
--- /dev/null
+++ b/tests/lib/Http/Client/ResponseTest.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * 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;
+
+use GuzzleHttp\Psr7\Response as GuzzleResponse;
+use GuzzleHttp\Psr7\Utils;
+use OC\Http\Client\Response;
+
+/**
+ * Class ResponseTest
+ */
+class ResponseTest extends \Test\TestCase {
+ /** @var GuzzleResponse */
+ private $guzzleResponse;
+
+ protected function setUp(): void {
+ parent::setUp();
+ $this->guzzleResponse = new GuzzleResponse(418);
+ }
+
+ public function testGetBody(): void {
+ $response = new Response($this->guzzleResponse->withBody(Utils::streamFor('MyResponse')));
+ $this->assertSame('MyResponse', $response->getBody());
+ }
+
+ public function testGetStatusCode(): void {
+ $response = new Response($this->guzzleResponse);
+ $this->assertSame(418, $response->getStatusCode());
+ }
+
+ public function testGetHeader(): void {
+ $response = new Response($this->guzzleResponse->withHeader('bar', 'foo'));
+ $this->assertSame('foo', $response->getHeader('bar'));
+ }
+
+ public function testGetHeaders(): void {
+ $response = new Response($this->guzzleResponse
+ ->withHeader('bar', 'foo')
+ ->withHeader('x-awesome', 'yes')
+ );
+
+ $expected = [
+ 'bar' => [
+ 0 => 'foo',
+ ],
+ 'x-awesome' => [
+ 0 => 'yes',
+ ],
+ ];
+ $this->assertSame($expected, $response->getHeaders());
+ $this->assertSame('yes', $response->getHeader('x-awesome'));
+ }
+}
diff --git a/tests/lib/Http/WellKnown/GenericResponseTest.php b/tests/lib/Http/WellKnown/GenericResponseTest.php
new file mode 100644
index 00000000000..f35f43221b8
--- /dev/null
+++ b/tests/lib/Http/WellKnown/GenericResponseTest.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Tests\Http\WellKnown;
+
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Http\WellKnown\GenericResponse;
+use Test\TestCase;
+
+class GenericResponseTest extends TestCase {
+ public function testToHttpResponse(): void {
+ $httpResponse = $this->createMock(JSONResponse::class);
+
+ $response = new GenericResponse($httpResponse);
+
+ self::assertSame($httpResponse, $response->toHttpResponse());
+ }
+}
diff --git a/tests/lib/Http/WellKnown/JrdResponseTest.php b/tests/lib/Http/WellKnown/JrdResponseTest.php
new file mode 100644
index 00000000000..375f244d618
--- /dev/null
+++ b/tests/lib/Http/WellKnown/JrdResponseTest.php
@@ -0,0 +1,92 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Http\WellKnown;
+
+use OCP\AppFramework\Http\JSONResponse;
+use OCP\Http\WellKnown\JrdResponse;
+use Test\TestCase;
+
+class JrdResponseTest extends TestCase {
+ public function testEmptyToHttpResponse(): void {
+ $response = new JrdResponse('subject');
+ $httpResponse = $response->toHttpResponse();
+
+ self::assertTrue($response->isEmpty());
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ /** @var JSONResponse $httpResponse */
+ self::assertEquals(
+ [
+ 'subject' => 'subject',
+ ],
+ $httpResponse->getData()
+ );
+ }
+
+ public function testComplexToHttpResponse(): void {
+ $response = new JrdResponse('subject');
+ $response->addAlias('alias');
+ $response->addAlias('blias');
+ $response->addProperty('propa', 'a');
+ $response->addProperty('propb', null);
+ $response->setExpires('tomorrow');
+ $response->addLink('rel', null, null);
+ $response->addLink('rel', 'type', null);
+ $response->addLink('rel', 'type', 'href', ['title' => 'titlevalue']);
+ $response->addLink('rel', 'type', 'href', ['title' => 'titlevalue'], ['propx' => 'valx']);
+ $httpResponse = $response->toHttpResponse();
+
+ self::assertFalse($response->isEmpty());
+ self::assertInstanceOf(JSONResponse::class, $httpResponse);
+ /** @var JSONResponse $httpResponse */
+ self::assertEquals(
+ [
+ 'subject' => 'subject',
+ 'aliases' => [
+ 'alias',
+ 'blias',
+ ],
+ 'properties' => [
+ 'propa' => 'a',
+ 'propb' => null,
+ ],
+ 'expires' => 'tomorrow',
+ 'links' => [
+ [
+ 'rel' => 'rel',
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ 'href' => 'href',
+ 'titles' => [
+ 'title' => 'titlevalue',
+ ],
+ ],
+ [
+ 'rel' => 'rel',
+ 'type' => 'type',
+ 'href' => 'href',
+ 'titles' => [
+ 'title' => 'titlevalue',
+ ],
+ 'properties' => [
+ 'propx' => 'valx',
+ ],
+ ],
+ ]
+ ],
+ $httpResponse->getData()
+ );
+ }
+}
diff --git a/tests/lib/Http/WellKnown/RequestManagerTest.php b/tests/lib/Http/WellKnown/RequestManagerTest.php
new file mode 100644
index 00000000000..3ab6cb190b2
--- /dev/null
+++ b/tests/lib/Http/WellKnown/RequestManagerTest.php
@@ -0,0 +1,154 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace Test\Http\WellKnown;
+
+use OC\AppFramework\Bootstrap\Coordinator;
+use OC\AppFramework\Bootstrap\RegistrationContext;
+use OC\AppFramework\Bootstrap\ServiceRegistration;
+use OC\Http\WellKnown\RequestManager;
+use OCP\AppFramework\QueryException;
+use OCP\Http\WellKnown\IHandler;
+use OCP\Http\WellKnown\IRequestContext;
+use OCP\Http\WellKnown\IResponse;
+use OCP\Http\WellKnown\JrdResponse;
+use OCP\IRequest;
+use OCP\IServerContainer;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use RuntimeException;
+use Test\TestCase;
+use function get_class;
+
+class RequestManagerTest extends TestCase {
+ /** @var Coordinator|MockObject */
+ private $coordinator;
+
+ /** @var IServerContainer|MockObject */
+ private $container;
+
+ /** @var MockObject|LoggerInterface */
+ private $logger;
+
+ /** @var RequestManager */
+ private $manager;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->coordinator = $this->createMock(Coordinator::class);
+ $this->container = $this->createMock(IServerContainer::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->manager = new RequestManager(
+ $this->coordinator,
+ $this->container,
+ $this->logger,
+ );
+ }
+
+ public function testProcessAppsNotRegistered(): void {
+ $request = $this->createMock(IRequest::class);
+ $this->expectException(RuntimeException::class);
+
+ $this->manager->process('webfinger', $request);
+ }
+
+ public function testProcessNoHandlersRegistered(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([]);
+
+ $response = $this->manager->process('webfinger', $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcessHandlerNotLoadable(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class {
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ new ServiceRegistration('test', get_class($handler)),
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willThrowException(new QueryException(''));
+ $this->logger->expects(self::once())
+ ->method('error');
+
+ $response = $this->manager->process('webfinger', $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcessHandlerOfWrongType(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class {
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ new ServiceRegistration('test', get_class($handler)),
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willReturn($handler);
+ $this->logger->expects(self::once())
+ ->method('error');
+
+ $response = $this->manager->process('webfinger', $request);
+
+ self::assertNull($response);
+ }
+
+ public function testProcess(): void {
+ $request = $this->createMock(IRequest::class);
+ $registrationContext = $this->createMock(RegistrationContext::class);
+ $this->coordinator->expects(self::once())
+ ->method('getRegistrationContext')
+ ->willReturn($registrationContext);
+ $handler = new class implements IHandler {
+ public function handle(string $service, IRequestContext $context, ?IResponse $previousResponse): ?IResponse {
+ return (new JrdResponse($service))->addAlias('alias');
+ }
+ };
+ $registrationContext->expects(self::once())
+ ->method('getWellKnownHandlers')
+ ->willReturn([
+ new ServiceRegistration('test', get_class($handler)),
+ ]);
+ $this->container->expects(self::once())
+ ->method('get')
+ ->with(get_class($handler))
+ ->willReturn($handler);
+
+ $response = $this->manager->process('webfinger', $request);
+
+ self::assertNotNull($response);
+ self::assertInstanceOf(JrdResponse::class, $response);
+ }
+}