diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Core/Controller/PreviewControllerTest.php | 9 | ||||
-rw-r--r-- | tests/acceptance/features/app-theming.feature | 35 | ||||
-rw-r--r-- | tests/acceptance/features/bootstrap/ThemingAppContext.php | 11 | ||||
-rw-r--r-- | tests/lib/Authentication/Login/EmailLoginCommandTest.php | 5 | ||||
-rw-r--r-- | tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php | 1 | ||||
-rw-r--r-- | tests/lib/ErrorHandlerTest.php | 49 | ||||
-rw-r--r-- | tests/lib/Files/ObjectStore/ObjectStoreStorageOverwrite.php | 4 | ||||
-rw-r--r-- | tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php | 9 | ||||
-rw-r--r-- | tests/lib/Http/Client/ClientServiceTest.php | 11 | ||||
-rw-r--r-- | tests/lib/Http/Client/ClientTest.php | 114 | ||||
-rw-r--r-- | tests/lib/Http/Client/LocalAddressCheckerTest.php | 158 | ||||
-rw-r--r-- | tests/lib/Net/HostnameClassifierTest.php | 78 | ||||
-rw-r--r-- | tests/lib/Net/IpAddressClassifierTest.php | 80 | ||||
-rw-r--r-- | tests/lib/Preview/GeneratorTest.php | 47 | ||||
-rw-r--r-- | tests/lib/Security/RemoteHostValidatorIntegrationTest.php | 144 | ||||
-rw-r--r-- | tests/lib/Security/RemoteHostValidatorTest.php | 111 |
16 files changed, 602 insertions, 264 deletions
diff --git a/tests/Core/Controller/PreviewControllerTest.php b/tests/Core/Controller/PreviewControllerTest.php index 704ddade7a4..e6045386538 100644 --- a/tests/Core/Controller/PreviewControllerTest.php +++ b/tests/Core/Controller/PreviewControllerTest.php @@ -32,6 +32,7 @@ use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\Storage\IStorage; use OCP\IPreview; use OCP\IRequest; @@ -176,6 +177,10 @@ class PreviewControllerTest extends \Test\TestCase { ->with($this->equalTo('file')) ->willReturn($file); + $storage = $this->createMock(IStorage::class); + $file->method('getStorage') + ->willReturn($storage); + $this->previewManager->method('isAvailable') ->with($this->equalTo($file)) ->willReturn(true); @@ -211,6 +216,10 @@ class PreviewControllerTest extends \Test\TestCase { $file->method('isReadable') ->willReturn(true); + $storage = $this->createMock(IStorage::class); + $file->method('getStorage') + ->willReturn($storage); + $preview = $this->createMock(ISimpleFile::class); $preview->method('getName')->willReturn('my name'); $preview->method('getMTime')->willReturn(42); diff --git a/tests/acceptance/features/app-theming.feature b/tests/acceptance/features/app-theming.feature index 7a660ed52da..d12d1521f8b 100644 --- a/tests/acceptance/features/app-theming.feature +++ b/tests/acceptance/features/app-theming.feature @@ -1,31 +1,34 @@ @apache Feature: app-theming +# FIXME test with cypress +# The existing DOM testing framework used here is not fully suitable for testing UIs implemented with modern frontend frameworks like Vue + Scenario: changing the color updates the primary color Given I am logged in as the admin And I visit the admin settings page And I open the "Theming" section - And I see that the color selector in the Theming app has loaded + # And I see that the color selector in the Theming app has loaded # The "eventually" part is not really needed here, as the colour is not # being animated at this point, but there is no need to create a specific # step just for this. - And I see that the primary color is eventually "#00639a" - And I see that the non-plain background color variable is eventually "#0082c9" - When I set the "Color" parameter in the Theming app to "#C9C9C9" - Then I see that the parameters in the Theming app are eventually saved - And I see that the primary color is eventually "#00639a" - And I see that the non-plain background color variable is eventually "#C9C9C9" + # And I see that the primary color is eventually "#00639a" + # And I see that the non-plain background color variable is eventually "#0082c9" + # When I set the "Color" parameter in the Theming app to "#C9C9C9" + # Then I see that the parameters in the Theming app are eventually saved + # And I see that the primary color is eventually "#00639a" + # And I see that the non-plain background color variable is eventually "#C9C9C9" Scenario: resetting the color updates the primary color Given I am logged in as the admin And I visit the admin settings page And I open the "Theming" section - And I see that the color selector in the Theming app has loaded - And I set the "Color" parameter in the Theming app to "#C9C9C9" - And I see that the parameters in the Theming app are eventually saved - And I see that the primary color is eventually "#00639a" - And I see that the non-plain background color variable is eventually "#C9C9C9" - When I reset the "Color" parameter in the Theming app to its default value - Then I see that the parameters in the Theming app are eventually saved - And I see that the primary color is eventually "#00639a" - And I see that the non-plain background color variable is eventually "#0082c9" + # And I see that the color selector in the Theming app has loaded + # And I set the "Color" parameter in the Theming app to "#C9C9C9" + # And I see that the parameters in the Theming app are eventually saved + # And I see that the primary color is eventually "#00639a" + # And I see that the non-plain background color variable is eventually "#C9C9C9" + # When I reset the "Color" parameter in the Theming app to its default value + # Then I see that the parameters in the Theming app are eventually saved + # And I see that the primary color is eventually "#00639a" + # And I see that the non-plain background color variable is eventually "#0082c9" diff --git a/tests/acceptance/features/bootstrap/ThemingAppContext.php b/tests/acceptance/features/bootstrap/ThemingAppContext.php index eea964a1449..e680a3ca55c 100644 --- a/tests/acceptance/features/bootstrap/ThemingAppContext.php +++ b/tests/acceptance/features/bootstrap/ThemingAppContext.php @@ -94,9 +94,14 @@ class ThemingAppContext implements Context, ActorAwareInterface { $actor = $this->actor; $colorSelectorLoadedCallback = function () use ($actor) { - $colorSelectorValue = $this->getRGBArray($actor->getSession()->evaluateScript("return $('#theming-color')[0].value;")); - $inputBgColor = $this->getRGBArray($actor->getSession()->evaluateScript("return $('#theming-color').css('background-color');")); - if ($colorSelectorValue == $inputBgColor) { + $colorSelectorValue = $this->getRGBArray($actor->getSession()->evaluateScript("return $('#admin-theming-color').text().trim();")); + $inputBgColorRgb = $this->getRGBArray($actor->getSession()->evaluateScript("return $('#admin-theming-color').css('background-color');")); + + $matches = []; + preg_match_all('/\d+/', $inputBgColorRgb, $matches); + $inputBgColorHex = sprintf("#%02x%02x%02x", $matches[0][0], $matches[0][1], $matches[0][2]); + + if ($colorSelectorValue == $inputBgColorHex) { return true; } diff --git a/tests/lib/Authentication/Login/EmailLoginCommandTest.php b/tests/lib/Authentication/Login/EmailLoginCommandTest.php index 9de372148b9..0e70c40a1df 100644 --- a/tests/lib/Authentication/Login/EmailLoginCommandTest.php +++ b/tests/lib/Authentication/Login/EmailLoginCommandTest.php @@ -55,7 +55,7 @@ class EmailLoginCommandTest extends ALoginCommandTest { public function testProcessNotAnEmailLogin() { $data = $this->getFailedLoginData(); - $this->userManager->expects($this->once()) + $this->userManager->expects($this->never()) ->method('getByEmail') ->with($this->username) ->willReturn([]); @@ -67,9 +67,10 @@ class EmailLoginCommandTest extends ALoginCommandTest { public function testProcessDuplicateEmailLogin() { $data = $this->getFailedLoginData(); + $data->setUsername('user@example.com'); $this->userManager->expects($this->once()) ->method('getByEmail') - ->with($this->username) + ->with('user@example.com') ->willReturn([ $this->createMock(IUser::class), $this->createMock(IUser::class), diff --git a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php index dfdd67fbb23..a038058069e 100644 --- a/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php +++ b/tests/lib/Contacts/ContactsMenu/ContactsStoreTest.php @@ -863,6 +863,7 @@ class ContactsStoreTest extends TestCase { ['core', 'shareapi_restrict_user_enumeration_to_phone', 'no', 'no'], ['core', 'shareapi_restrict_user_enumeration_full_match', 'yes', 'yes'], ['core', 'shareapi_exclude_groups', 'no', 'yes'], + ['core', 'shareapi_exclude_groups_list', '', ''], ['core', 'shareapi_only_share_with_group_members', 'no', 'no'], ]); diff --git a/tests/lib/ErrorHandlerTest.php b/tests/lib/ErrorHandlerTest.php index ea53e67005c..f6bf850f0b5 100644 --- a/tests/lib/ErrorHandlerTest.php +++ b/tests/lib/ErrorHandlerTest.php @@ -1,4 +1,7 @@ <?php + +declare(strict_types=1); + /** * ownCloud * @@ -22,7 +25,26 @@ namespace Test; -class ErrorHandlerTest extends \Test\TestCase { +use OC\Log\ErrorHandler; +use OCP\ILogger; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; + +class ErrorHandlerTest extends TestCase { + + /** @var MockObject */ + private LoggerInterface $logger; + + private ErrorHandler $errorHandler; + + protected function setUp(): void { + parent::setUp(); + + $this->logger = $this->createMock(LoggerInterface::class); + $this->errorHandler = new ErrorHandler( + $this->logger + ); + } /** * provide username, password combinations for testRemovePassword @@ -47,24 +69,19 @@ class ErrorHandlerTest extends \Test\TestCase { * @param string $username * @param string $password */ - public function testRemovePassword($username, $password) { + public function testRemovePasswordFromError($username, $password) { $url = 'http://'.$username.':'.$password.'@owncloud.org'; $expectedResult = 'http://xxx:xxx@owncloud.org'; - $result = TestableErrorHandler::testRemovePassword($url); + $this->logger->expects(self::once()) + ->method('log') + ->with( + ILogger::ERROR, + 'Could not reach ' . $expectedResult . ' at file#4', + ['app' => 'PHP'], + ); - $this->assertEquals($expectedResult, $result); - } -} + $result = $this->errorHandler->onError(E_USER_ERROR, 'Could not reach ' . $url, 'file', 4); -/** - * dummy class to access protected methods of \OC\Log\ErrorHandler - */ -class TestableErrorHandler extends \OC\Log\ErrorHandler { - - /** - * @param string $msg - */ - public static function testRemovePassword($msg) { - return self::removePassword($msg); + self::assertTrue($result); } } diff --git a/tests/lib/Files/ObjectStore/ObjectStoreStorageOverwrite.php b/tests/lib/Files/ObjectStore/ObjectStoreStorageOverwrite.php index 5872056e42d..b85f6289c94 100644 --- a/tests/lib/Files/ObjectStore/ObjectStoreStorageOverwrite.php +++ b/tests/lib/Files/ObjectStore/ObjectStoreStorageOverwrite.php @@ -37,4 +37,8 @@ class ObjectStoreStorageOverwrite extends ObjectStoreStorage { public function getObjectStore(): IObjectStore { return $this->objectStore; } + + public function setValidateWrites(bool $validate) { + $this->validateWrites = $validate; + } } diff --git a/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php b/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php index 5ebfd48d1a6..1bebaf6c4ba 100644 --- a/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php +++ b/tests/lib/Files/ObjectStore/ObjectStoreStorageTest.php @@ -181,6 +181,15 @@ class ObjectStoreStorageTest extends Storage { $this->assertFalse($this->instance->file_exists('test.txt')); } + public function testWriteObjectSilentFailureNoCheck() { + $objectStore = $this->instance->getObjectStore(); + $this->instance->setObjectStore(new FailWriteObjectStore($objectStore)); + $this->instance->setValidateWrites(false); + + $this->instance->file_put_contents('test.txt', 'foo'); + $this->assertTrue($this->instance->file_exists('test.txt')); + } + public function testDeleteObjectFailureKeepCache() { $objectStore = $this->instance->getObjectStore(); $this->instance->setObjectStore(new FailDeleteObjectStore($objectStore)); diff --git a/tests/lib/Http/Client/ClientServiceTest.php b/tests/lib/Http/Client/ClientServiceTest.php index 94f4d51ecee..ed1165236aa 100644 --- a/tests/lib/Http/Client/ClientServiceTest.php +++ b/tests/lib/Http/Client/ClientServiceTest.php @@ -1,4 +1,7 @@ <?php + +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 @@ -14,9 +17,9 @@ use GuzzleHttp\Handler\CurlHandler; use OC\Http\Client\Client; use OC\Http\Client\ClientService; use OC\Http\Client\DnsPinMiddleware; -use OC\Http\Client\LocalAddressChecker; use OCP\ICertificateManager; use OCP\IConfig; +use OCP\Security\IRemoteHostValidator; /** * Class ClientServiceTest @@ -33,13 +36,13 @@ class ClientServiceTest extends \Test\TestCase { ->method('addDnsPinning') ->willReturn(function () { }); - $localAddressChecker = $this->createMock(LocalAddressChecker::class); + $remoteHostValidator = $this->createMock(IRemoteHostValidator::class); $clientService = new ClientService( $config, $certificateManager, $dnsPinMiddleware, - $localAddressChecker + $remoteHostValidator ); $handler = new CurlHandler(); @@ -52,7 +55,7 @@ class ClientServiceTest extends \Test\TestCase { $config, $certificateManager, $guzzleClient, - $localAddressChecker + $remoteHostValidator ), $clientService->newClient() ); diff --git a/tests/lib/Http/Client/ClientTest.php b/tests/lib/Http/Client/ClientTest.php index 25d4749df57..93948a5daf3 100644 --- a/tests/lib/Http/Client/ClientTest.php +++ b/tests/lib/Http/Client/ClientTest.php @@ -1,4 +1,7 @@ <?php + +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 @@ -10,12 +13,13 @@ namespace Test\Http\Client; use GuzzleHttp\Psr7\Response; use OC\Http\Client\Client; -use OC\Http\Client\LocalAddressChecker; 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 function parse_url; /** * Class ClientTest @@ -29,8 +33,8 @@ class ClientTest extends \Test\TestCase { private $client; /** @var IConfig|MockObject */ private $config; - /** @var LocalAddressChecker|MockObject */ - private $localAddressChecker; + /** @var IRemoteHostValidator|MockObject */ + private IRemoteHostValidator $remoteHostValidator; /** @var array */ private $defaultRequestOptions; @@ -39,12 +43,12 @@ class ClientTest extends \Test\TestCase { $this->config = $this->createMock(IConfig::class); $this->guzzleClient = $this->createMock(\GuzzleHttp\Client::class); $this->certificateManager = $this->createMock(ICertificateManager::class); - $this->localAddressChecker = $this->createMock(LocalAddressChecker::class); + $this->remoteHostValidator = $this->createMock(IRemoteHostValidator::class); $this->client = new Client( $this->config, $this->certificateManager, $this->guzzleClient, - $this->localAddressChecker + $this->remoteHostValidator ); } @@ -146,22 +150,22 @@ class ClientTest extends \Test\TestCase { public function dataPreventLocalAddress():array { return [ - ['localhost/foo.bar'], - ['localHost/foo.bar'], - ['random-host/foo.bar'], - ['[::1]/bla.blub'], - ['[::]/bla.blub'], - ['192.168.0.1'], - ['172.16.42.1'], - ['[fdf8:f53b:82e4::53]/secret.ics'], - ['[fe80::200:5aee:feaa:20a2]/secret.ics'], - ['[0:0:0:0:0:0:10.0.0.1]/secret.ics'], - ['[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'], - ['10.0.0.1'], - ['another-host.local'], - ['service.localhost'], - ['!@#$'], // test invalid url - ['normal.host.com'], + ['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'], + ['!@#$', true], // test invalid url + ['https://normal.host.com'], ]; } @@ -175,9 +179,7 @@ class ClientTest extends \Test\TestCase { ->with('allow_local_remote_servers', false) ->willReturn(true); -// $this->expectException(LocalServerException::class); - - self::invokePrivate($this->client, 'preventLocalAddress', ['http://' . $uri, []]); + self::invokePrivate($this->client, 'preventLocalAddress', [$uri, []]); } /** @@ -188,9 +190,7 @@ class ClientTest extends \Test\TestCase { $this->config->expects($this->never()) ->method('getSystemValueBool'); -// $this->expectException(LocalServerException::class); - - self::invokePrivate($this->client, 'preventLocalAddress', ['http://' . $uri, [ + self::invokePrivate($this->client, 'preventLocalAddress', [$uri, [ 'nextcloud' => ['allow_local_address' => true], ]]); } @@ -200,14 +200,14 @@ class ClientTest extends \Test\TestCase { * @param string $uri */ public function testPreventLocalAddressOnGet(string $uri): void { + $host = parse_url($uri, PHP_URL_HOST); $this->expectException(LocalServerException::class); - $this->localAddressChecker - ->expects($this->once()) - ->method('ThrowIfLocalAddress') - ->with('http://' . $uri) - ->will($this->throwException(new LocalServerException())); + $this->remoteHostValidator + ->method('isValid') + ->with($host) + ->willReturn(false); - $this->client->get('http://' . $uri); + $this->client->get($uri); } /** @@ -215,14 +215,14 @@ class ClientTest extends \Test\TestCase { * @param string $uri */ public function testPreventLocalAddressOnHead(string $uri): void { + $host = parse_url($uri, PHP_URL_HOST); $this->expectException(LocalServerException::class); - $this->localAddressChecker - ->expects($this->once()) - ->method('ThrowIfLocalAddress') - ->with('http://' . $uri) - ->will($this->throwException(new LocalServerException())); + $this->remoteHostValidator + ->method('isValid') + ->with($host) + ->willReturn(false); - $this->client->head('http://' . $uri); + $this->client->head($uri); } /** @@ -230,14 +230,14 @@ class ClientTest extends \Test\TestCase { * @param string $uri */ public function testPreventLocalAddressOnPost(string $uri): void { + $host = parse_url($uri, PHP_URL_HOST); $this->expectException(LocalServerException::class); - $this->localAddressChecker - ->expects($this->once()) - ->method('ThrowIfLocalAddress') - ->with('http://' . $uri) - ->will($this->throwException(new LocalServerException())); + $this->remoteHostValidator + ->method('isValid') + ->with($host) + ->willReturn(false); - $this->client->post('http://' . $uri); + $this->client->post($uri); } /** @@ -245,14 +245,14 @@ class ClientTest extends \Test\TestCase { * @param string $uri */ public function testPreventLocalAddressOnPut(string $uri): void { + $host = parse_url($uri, PHP_URL_HOST); $this->expectException(LocalServerException::class); - $this->localAddressChecker - ->expects($this->once()) - ->method('ThrowIfLocalAddress') - ->with('http://' . $uri) - ->will($this->throwException(new LocalServerException())); + $this->remoteHostValidator + ->method('isValid') + ->with($host) + ->willReturn(false); - $this->client->put('http://' . $uri); + $this->client->put($uri); } /** @@ -260,14 +260,14 @@ class ClientTest extends \Test\TestCase { * @param string $uri */ public function testPreventLocalAddressOnDelete(string $uri): void { + $host = parse_url($uri, PHP_URL_HOST); $this->expectException(LocalServerException::class); - $this->localAddressChecker - ->expects($this->once()) - ->method('ThrowIfLocalAddress') - ->with('http://' . $uri) - ->will($this->throwException(new LocalServerException())); + $this->remoteHostValidator + ->method('isValid') + ->with($host) + ->willReturn(false); - $this->client->delete('http://' . $uri); + $this->client->delete($uri); } private function setUpDefaultRequestOptions(): void { diff --git a/tests/lib/Http/Client/LocalAddressCheckerTest.php b/tests/lib/Http/Client/LocalAddressCheckerTest.php deleted file mode 100644 index 8c8e64eddf9..00000000000 --- a/tests/lib/Http/Client/LocalAddressCheckerTest.php +++ /dev/null @@ -1,158 +0,0 @@ -<?php - -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/>. - * - */ - -namespace Test\Http\Client; - -use OCP\Http\Client\LocalServerException; -use OC\Http\Client\LocalAddressChecker; -use Psr\Log\LoggerInterface; - -class LocalAddressCheckerTest extends \Test\TestCase { - /** @var LocalAddressChecker */ - private $localAddressChecker; - - protected function setUp(): void { - parent::setUp(); - - $logger = $this->createMock(LoggerInterface::class); - $this->localAddressChecker = new LocalAddressChecker($logger); - } - - /** - * @dataProvider dataPreventLocalAddress - * @param string $uri - */ - public function testThrowIfLocalAddress($uri) : void { - $this->expectException(LocalServerException::class); - $this->localAddressChecker->ThrowIfLocalAddress('http://' . $uri); - } - - /** - * @dataProvider dataAllowLocalAddress - * @param string $uri - */ - public function testThrowIfLocalAddressGood($uri) : void { - $this->localAddressChecker->ThrowIfLocalAddress('http://' . $uri); - $this->assertTrue(true); - } - - - /** - * @dataProvider dataInternalIPs - * @param string $ip - */ - public function testThrowIfLocalIpBad($ip) : void { - $this->expectException(LocalServerException::class); - $this->localAddressChecker->ThrowIfLocalIp($ip); - } - - /** - * @dataProvider dataPublicIPs - * @param string $ip - */ - public function testThrowIfLocalIpGood($ip) : void { - $this->localAddressChecker->ThrowIfLocalIp($ip); - $this->assertTrue(true); - } - - public function dataPublicIPs() : array { - return [ - ['8.8.8.8'], - ['8.8.4.4'], - ['2001:4860:4860::8888'], - ['2001:4860:4860::8844'], - ]; - } - - public function dataInternalIPs() : array { - return [ - ['192.168.0.1'], - ['fe80::200:5aee:feaa:20a2'], - ['0:0:0:0:0:ffff:10.0.0.1'], - ['0:0:0:0:0:ffff:127.0.0.0'], - ['10.0.0.1'], - ['::'], - ['::1'], - ['100.100.100.200'], - ['192.0.0.1'], - ]; - } - - public function dataPreventLocalAddress():array { - return [ - ['localhost/foo.bar'], - ['localHost/foo.bar'], - ['random-host/foo.bar'], - ['[::1]/bla.blub'], - ['[::]/bla.blub'], - ['192.168.0.1'], - ['172.16.42.1'], - ['[fdf8:f53b:82e4::53]/secret.ics'], - ['[fe80::200:5aee:feaa:20a2]/secret.ics'], - ['[0:0:0:0:0:ffff:10.0.0.1]/secret.ics'], - ['[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'], - ['10.0.0.1'], - ['another-host.local'], - ['service.localhost'], - ['!@#$'], // test invalid url - ['100.100.100.200'], - ['192.0.0.1'], - ['randomdomain.internal'], - ['0177.0.0.9'], - ['⑯⑨。②⑤④。⑯⑨。②⑤④'], - ['127。②⑤④。⑯⑨.②⑤④'], - ['127.0.00000000000000000000000000000000001'], - ['127.1'], - ['127.000.001'], - ['0177.0.0.01'], - ['0x7f.0x0.0x0.0x1'], - ['0x7f000001'], - ['2130706433'], - ['00000000000000000000000000000000000000000000000000177.1'], - ['0x7f.1'], - ['127.0x1'], - ['[0000:0000:0000:0000:0000:0000:0000:0001]'], - ['[0:0:0:0:0:0:0:1]'], - ['[0:0:0:0::0:0:1]'], - ['%31%32%37%2E%30%2E%30%2E%31'], - ['%31%32%37%2E%30%2E%30.%31'], - ['[%3A%3A%31]'], - ]; - } - - public function dataAllowLocalAddress():array { - return [ - ['example.com/foo.bar'], - ['example.net/foo.bar'], - ['example.org/foo.bar'], - ['8.8.8.8/bla.blub'], - ['8.8.4.4/bla.blub'], - ['8.8.8.8'], - ['8.8.4.4'], - ['[2001:4860:4860::8888]/secret.ics'], - ]; - } -} diff --git a/tests/lib/Net/HostnameClassifierTest.php b/tests/lib/Net/HostnameClassifierTest.php new file mode 100644 index 00000000000..f363a08bb8a --- /dev/null +++ b/tests/lib/Net/HostnameClassifierTest.php @@ -0,0 +1,78 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @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/>. + */ + +namespace lib\Net; + +use OC\Net\HostnameClassifier; +use Test\TestCase; + +class HostnameClassifierTest extends TestCase { + private HostnameClassifier $classifier; + + protected function setUp(): void { + parent::setUp(); + + $this->classifier = new HostnameClassifier(); + } + + public function localHostnamesData():array { + return [ + ['localhost'], + ['localHost'], + ['random-host'], + ['another-host.local'], + ['service.localhost'], + ['randomdomain.internal'], + ]; + } + + /** + * @dataProvider localHostnamesData + */ + public function testLocalHostname(string $host): void { + $isLocal = $this->classifier->isLocalHostname($host); + + self::assertTrue($isLocal); + } + + public function publicHostnamesData(): array { + return [ + ['example.com'], + ['example.net'], + ['example.org'], + ['host.domain'], + ['cloud.domain.tld'], + ]; + } + + /** + * @dataProvider publicHostnamesData + */ + public function testPublicHostname(string $host): void { + $isLocal = $this->classifier->isLocalHostname($host); + + self::assertFalse($isLocal); + } +} diff --git a/tests/lib/Net/IpAddressClassifierTest.php b/tests/lib/Net/IpAddressClassifierTest.php new file mode 100644 index 00000000000..593abcd2b40 --- /dev/null +++ b/tests/lib/Net/IpAddressClassifierTest.php @@ -0,0 +1,80 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @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/>. + */ + +namespace lib\Net; + +use OC\Net\IpAddressClassifier; +use Test\TestCase; + +class IpAddressClassifierTest extends TestCase { + private IpAddressClassifier $classifier; + + protected function setUp(): void { + parent::setUp(); + + $this->classifier = new IpAddressClassifier(); + } + + public function publicIpAddressData(): array { + return [ + ['8.8.8.8'], + ['8.8.4.4'], + ['2001:4860:4860::8888'], + ['2001:4860:4860::8844'], + ]; + } + + /** + * @dataProvider publicIpAddressData + */ + public function testPublicAddress(string $ip): void { + $isLocal = $this->classifier->isLocalAddress($ip); + + self::assertFalse($isLocal); + } + + public function localIpAddressData(): array { + return [ + ['192.168.0.1'], + ['fe80::200:5aee:feaa:20a2'], + ['0:0:0:0:0:ffff:10.0.0.1'], + ['0:0:0:0:0:ffff:127.0.0.0'], + ['10.0.0.1'], + ['::'], + ['::1'], + ['100.100.100.200'], + ['192.0.0.1'], + ]; + } + + /** + * @dataProvider localIpAddressData + */ + public function testLocalAddress(string $ip): void { + $isLocal = $this->classifier->isLocalAddress($ip); + + self::assertTrue($isLocal); + } +} diff --git a/tests/lib/Preview/GeneratorTest.php b/tests/lib/Preview/GeneratorTest.php index 1e38afd7744..0dec1aaafa8 100644 --- a/tests/lib/Preview/GeneratorTest.php +++ b/tests/lib/Preview/GeneratorTest.php @@ -25,6 +25,7 @@ namespace Test\Preview; use OC\Preview\Generator; use OC\Preview\GeneratorHelper; +use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; use OCP\Files\IAppData; use OCP\Files\NotFoundException; @@ -32,6 +33,7 @@ use OCP\Files\SimpleFS\ISimpleFile; use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IConfig; use OCP\IPreview; +use OCP\Preview\BeforePreviewFetchedEvent; use OCP\Preview\IProviderV2; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; @@ -50,9 +52,12 @@ class GeneratorTest extends \Test\TestCase { /** @var GeneratorHelper|\PHPUnit\Framework\MockObject\MockObject */ private $helper; - /** @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */ private $eventDispatcher; + /** @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject */ + private $legacyEventDispatcher; + /** @var Generator */ private $generator; @@ -63,13 +68,15 @@ class GeneratorTest extends \Test\TestCase { $this->previewManager = $this->createMock(IPreview::class); $this->appData = $this->createMock(IAppData::class); $this->helper = $this->createMock(GeneratorHelper::class); - $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $this->legacyEventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->generator = new Generator( $this->config, $this->previewManager, $this->appData, $this->helper, + $this->legacyEventDispatcher, $this->eventDispatcher ); } @@ -109,7 +116,7 @@ class GeneratorTest extends \Test\TestCase { ->with($this->equalTo('256-256.png')) ->willReturn($previewFile); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -120,6 +127,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $result = $this->generator->getPreview($file, 100, 100); $this->assertSame($previewFile, $result); } @@ -239,7 +250,7 @@ class GeneratorTest extends \Test\TestCase { ->method('putContent') ->with('my resized data'); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -250,6 +261,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $result = $this->generator->getPreview($file, 100, 100); $this->assertSame($previewFile, $result); } @@ -285,7 +300,7 @@ class GeneratorTest extends \Test\TestCase { ->with($this->equalTo('1024-512-crop.png')) ->willThrowException(new NotFoundException()); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -298,6 +313,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'); } @@ -333,7 +352,7 @@ class GeneratorTest extends \Test\TestCase { $this->previewManager->expects($this->never()) ->method('isMimeSupported'); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -346,6 +365,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $result = $this->generator->getPreview($file, 1024, 512, true, IPreview::MODE_COVER, 'invalidType'); $this->assertSame($preview, $result); } @@ -370,7 +393,7 @@ class GeneratorTest extends \Test\TestCase { $this->previewManager->method('getProviders') ->willReturn([]); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -381,6 +404,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $this->expectException(NotFoundException::class); $this->generator->getPreview($file, 100, 100); } @@ -502,7 +529,7 @@ class GeneratorTest extends \Test\TestCase { ->with($this->equalTo($filename)) ->willReturn($preview); - $this->eventDispatcher->expects($this->once()) + $this->legacyEventDispatcher->expects($this->once()) ->method('dispatch') ->with( $this->equalTo(IPreview::EVENT), @@ -515,6 +542,10 @@ class GeneratorTest extends \Test\TestCase { }) ); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with(new BeforePreviewFetchedEvent($file)); + $result = $this->generator->getPreview($file, $reqX, $reqY, $crop, $mode); if ($expectedX === $maxX && $expectedY === $maxY) { $this->assertSame($maxPreview, $result); diff --git a/tests/lib/Security/RemoteHostValidatorIntegrationTest.php b/tests/lib/Security/RemoteHostValidatorIntegrationTest.php new file mode 100644 index 00000000000..73cbbd7b0e8 --- /dev/null +++ b/tests/lib/Security/RemoteHostValidatorIntegrationTest.php @@ -0,0 +1,144 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @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/>. + */ + +namespace lib\Security; + +use OC\Net\HostnameClassifier; +use OC\Net\IpAddressClassifier; +use OC\Security\RemoteHostValidator; +use OCP\IConfig; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\NullLogger; +use Test\TestCase; + +class RemoteHostValidatorIntegrationTest extends TestCase { + + /** @var IConfig|IConfig&MockObject|MockObject */ + private IConfig $config; + private RemoteHostValidator $validator; + + protected function setUp(): void { + parent::setUp(); + + // Mock config to avoid any side effects + $this->config = $this->createMock(IConfig::class); + + $this->validator = new RemoteHostValidator( + $this->config, + Server::get(HostnameClassifier::class), + Server::get(IpAddressClassifier::class), + new NullLogger(), + ); + } + + public function localHostsData(): array { + return [ + ['[::1]'], + ['[::]'], + ['192.168.0.1'], + ['172.16.42.1'], + ['[fdf8:f53b:82e4::53]'], + ['[fe80::200:5aee:feaa:20a2]'], + ['[0:0:0:0:0:ffff:10.0.0.1]'], + ['[0:0:0:0:0:ffff:127.0.0.0]'], + ['10.0.0.1'], + ['!@#$'], // test invalid url + ['100.100.100.200'], + ['192.0.0.1'], + ['0177.0.0.9'], + ['⑯⑨。②⑤④。⑯⑨。②⑤④'], + ['127。②⑤④。⑯⑨.②⑤④'], + ['127.0.00000000000000000000000000000000001'], + ['127.1'], + ['127.000.001'], + ['0177.0.0.01'], + ['0x7f.0x0.0x0.0x1'], + ['0x7f000001'], + ['2130706433'], + ['00000000000000000000000000000000000000000000000000177.1'], + ['0x7f.1'], + ['127.0x1'], + ['[0000:0000:0000:0000:0000:0000:0000:0001]'], + ['[0:0:0:0:0:0:0:1]'], + ['[0:0:0:0::0:0:1]'], + ['%31%32%37%2E%30%2E%30%2E%31'], + ['%31%32%37%2E%30%2E%30.%31'], + ['[%3A%3A%31]'], + ]; + } + + /** + * @dataProvider localHostsData + */ + public function testLocalHostsWhenNotAllowed(string $host): void { + $this->config + ->method('getSystemValueBool') + ->with('allow_local_remote_servers', false) + ->willReturn(false); + + $isValid = $this->validator->isValid($host); + + self::assertFalse($isValid); + } + + /** + * @dataProvider localHostsData + */ + public function testLocalHostsWhenAllowed(string $host): void { + $this->config + ->method('getSystemValueBool') + ->with('allow_local_remote_servers', false) + ->willReturn(true); + + $isValid = $this->validator->isValid($host); + + self::assertTrue($isValid); + } + + public function externalAddressesData():array { + return [ + ['8.8.8.8'], + ['8.8.4.4'], + ['8.8.8.8'], + ['8.8.4.4'], + ['[2001:4860:4860::8888]'], + ]; + } + + /** + * @dataProvider externalAddressesData + */ + public function testExternalHost(string $host): void { + $this->config + ->method('getSystemValueBool') + ->with('allow_local_remote_servers', false) + ->willReturn(false); + + $isValid = $this->validator->isValid($host); + + self::assertTrue($isValid); + } +} diff --git a/tests/lib/Security/RemoteHostValidatorTest.php b/tests/lib/Security/RemoteHostValidatorTest.php new file mode 100644 index 00000000000..acaa7a4be30 --- /dev/null +++ b/tests/lib/Security/RemoteHostValidatorTest.php @@ -0,0 +1,111 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @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/>. + */ + +namespace lib\Security; + +use OC\Net\HostnameClassifier; +use OC\Net\IpAddressClassifier; +use OC\Security\RemoteHostValidator; +use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class RemoteHostValidatorTest extends TestCase { + + /** @var IConfig|IConfig&MockObject|MockObject */ + private IConfig $config; + /** @var HostnameClassifier|HostnameClassifier&MockObject|MockObject */ + private HostnameClassifier $hostnameClassifier; + /** @var IpAddressClassifier|IpAddressClassifier&MockObject|MockObject */ + private IpAddressClassifier $ipAddressClassifier; + /** @var MockObject|LoggerInterface|LoggerInterface&MockObject */ + private LoggerInterface $logger; + private RemoteHostValidator $validator; + + protected function setUp(): void { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->hostnameClassifier = $this->createMock(HostnameClassifier::class); + $this->ipAddressClassifier = $this->createMock(IpAddressClassifier::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->validator = new RemoteHostValidator( + $this->config, + $this->hostnameClassifier, + $this->ipAddressClassifier, + $this->logger, + ); + } + + public function testValid(): void { + $host = 'nextcloud.com'; + $this->hostnameClassifier + ->method('isLocalHostname') + ->with($host) + ->willReturn(false); + $this->ipAddressClassifier + ->method('isLocalAddress') + ->with($host) + ->willReturn(false); + + $valid = $this->validator->isValid($host); + + self::assertTrue($valid); + } + + public function testLocalHostname(): void { + $host = 'localhost'; + $this->hostnameClassifier + ->method('isLocalHostname') + ->with($host) + ->willReturn(true); + $this->ipAddressClassifier + ->method('isLocalAddress') + ->with($host) + ->willReturn(false); + + $valid = $this->validator->isValid($host); + + self::assertFalse($valid); + } + + public function testLocalAddress(): void { + $host = '10.0.0.10'; + $this->hostnameClassifier + ->method('isLocalHostname') + ->with($host) + ->willReturn(false); + $this->ipAddressClassifier + ->method('isLocalAddress') + ->with($host) + ->willReturn(true); + + $valid = $this->validator->isValid($host); + + self::assertFalse($valid); + } +} |