diff --git a/app/Http/Requests/SiteRequest.php b/app/Http/Requests/SiteRequest.php index 76c2b9a..7027eaf 100644 --- a/app/Http/Requests/SiteRequest.php +++ b/app/Http/Requests/SiteRequest.php @@ -2,6 +2,7 @@ namespace App\Http\Requests; +use App\Rules\RemoteURL; use Illuminate\Foundation\Http\FormRequest; class SiteRequest extends FormRequest @@ -9,7 +10,13 @@ class SiteRequest extends FormRequest public function rules(): array { return [ - 'url' => 'required|url', + 'url' => [ + 'required', + 'string', + 'url', + 'max:255', + new RemoteURL() + ], 'key' => 'required|string|min:32|max:64', ]; } diff --git a/app/Network/DNSLookup.php b/app/Network/DNSLookup.php new file mode 100644 index 0000000..fe8777e --- /dev/null +++ b/app/Network/DNSLookup.php @@ -0,0 +1,29 @@ +getIPs($host); + + // Could not resolve given address + if (count($ips) === 0) { + $fail("Invalid URL: unresolvable site URL."); + } + + // Check each resolved IP + foreach ($ips as $ip) { + if (!filter_var( + $ip, + FILTER_VALIDATE_IP, + FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE + ) + ) { + $fail("Invalid URL: local address are disallowed as site URL."); + } + } + } +} diff --git a/tests/Feature/Api/SiteControllerTest.php b/tests/Feature/Api/SiteControllerTest.php index 3a6efd9..342f77d 100644 --- a/tests/Feature/Api/SiteControllerTest.php +++ b/tests/Feature/Api/SiteControllerTest.php @@ -123,7 +123,7 @@ public function testCheckingASiteReturns404ForInvalidSite(): void ["url" => "https://www.joomlaf.org", "key" => "foobar123foobar123foobar123foobar123"] ); - $response->assertStatus(404); + $response->assertStatus(422); } public function testDeleteASiteReturns404ForInvalidSite(): void @@ -133,7 +133,7 @@ public function testDeleteASiteReturns404ForInvalidSite(): void ["url" => "https://www.joomlaf.org", "key" => "foobar123foobar123foobar123foobar123"] ); - $response->assertStatus(404); + $response->assertStatus(422); } public function testDeleteASiteRemovesRow(): void diff --git a/tests/Unit/Network/DNSLookupTest.php b/tests/Unit/Network/DNSLookupTest.php new file mode 100644 index 0000000..88fafe2 --- /dev/null +++ b/tests/Unit/Network/DNSLookupTest.php @@ -0,0 +1,27 @@ +assertSame(['127.0.0.1'], $object->getIPs('127.0.0.1')); + } + + public function testEmptyArrayIsReturnedForInvalidHost() + { + $object = new DNSLookup(); + $this->assertSame([], $object->getIPs('invalid.host.with.bogus.tld')); + } + + public function testIpsAreReturned() + { + $object = new DNSLookup(); + $this->assertGreaterThan(5, $object->getIPs('joomla.org')); + } +} diff --git a/tests/Unit/Rules/RemoteURLTest.php b/tests/Unit/Rules/RemoteURLTest.php new file mode 100644 index 0000000..860ecf6 --- /dev/null +++ b/tests/Unit/Rules/RemoteURLTest.php @@ -0,0 +1,38 @@ +validate('url', $host, function ($message) use ($expectedResult, $expectedMessage) { + if (!$expectedResult) { + $this->assertTrue(true); + $this->assertSame($expectedMessage, $message); + } + }); + + if ($expectedResult) { + $this->assertTrue(true); + } + } + + public static function urlDataProvider(): array + { + return [ + ['https://127.0.0.1', false, 'Invalid URL: local address are disallowed as site URL.'], + ['https://localhost', false, 'Invalid URL: local address are disallowed as site URL.'], + ['https://10.0.0.1', false, 'Invalid URL: local address are disallowed as site URL.'], + ['https://joomla.org', true, ''], + ['https://invalid.host.tld', false,'Invalid URL: unresolvable site URL.'], + ]; + } +}