fix: accept underscores in domain hostnames for API URL validation
PHP's FILTER_VALIDATE_URL rejects underscores in the host, so domains like https://myapp_service.example.com were rejected by the API and never got a Let's Encrypt certificate. Add an isValidDomainUrl() helper that validates a copy with underscores replaced by hyphens, and route domain validation in the Applications and Services API controllers through it. Fixes #10597
This commit is contained in:
parent
52739141ee
commit
74b1077010
4 changed files with 39 additions and 7 deletions
|
|
@ -1084,7 +1084,7 @@ private function create_application(Request $request, $type)
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$urls = $urls->map(function ($url) use (&$errors) {
|
$urls = $urls->map(function ($url) use (&$errors) {
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = "Invalid URL: {$url}";
|
$errors[] = "Invalid URL: {$url}";
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
@ -1325,7 +1325,7 @@ private function create_application(Request $request, $type)
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$urls = $urls->map(function ($url) use (&$errors) {
|
$urls = $urls->map(function ($url) use (&$errors) {
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = "Invalid URL: {$url}";
|
$errors[] = "Invalid URL: {$url}";
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
@ -1538,7 +1538,7 @@ private function create_application(Request $request, $type)
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$urls = $urls->map(function ($url) use (&$errors) {
|
$urls = $urls->map(function ($url) use (&$errors) {
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = "Invalid URL: {$url}";
|
$errors[] = "Invalid URL: {$url}";
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
@ -2490,7 +2490,7 @@ public function update_by_uuid(Request $request)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = 'Invalid URL: '.$url;
|
$errors[] = 'Invalid URL: '.$url;
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
@ -2553,7 +2553,7 @@ public function update_by_uuid(Request $request)
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$urls = $urls->map(function ($url) use (&$errors) {
|
$urls = $urls->map(function ($url) use (&$errors) {
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = "Invalid URL: {$url}";
|
$errors[] = "Invalid URL: {$url}";
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
@ -3866,7 +3866,7 @@ private function validateDataApplications(Request $request, Server $server)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = 'Invalid URL: '.$url;
|
$errors[] = 'Invalid URL: '.$url;
|
||||||
|
|
||||||
return str($url)->lower();
|
return str($url)->lower();
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ private function applyServiceUrls(Service $service, array $urlsArray, string $te
|
||||||
});
|
});
|
||||||
|
|
||||||
$urls = $urls->map(function ($url) use (&$errors) {
|
$urls = $urls->map(function ($url) use (&$errors) {
|
||||||
if (! filter_var($url, FILTER_VALIDATE_URL)) {
|
if (! isValidDomainUrl($url)) {
|
||||||
$errors[] = "Invalid URL: {$url}";
|
$errors[] = "Invalid URL: {$url}";
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
function isValidDomainUrl(string $url): bool
|
||||||
|
{
|
||||||
|
// PHP's FILTER_VALIDATE_URL rejects underscores in the host, but they are
|
||||||
|
// accepted by browsers, Let's Encrypt, and common Docker service naming
|
||||||
|
// (e.g. https://myapp_service.example.com). Validate against a copy with
|
||||||
|
// underscores replaced by hyphens (a valid host character) so such domains
|
||||||
|
// are not wrongly rejected; URLs without underscores are unaffected.
|
||||||
|
return filter_var(str_replace('_', '-', $url), FILTER_VALIDATE_URL) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
function checkDomainUsage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
function checkDomainUsage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
|
||||||
{
|
{
|
||||||
$conflicts = [];
|
$conflicts = [];
|
||||||
|
|
|
||||||
22
tests/Unit/IsValidDomainUrlTest.php
Normal file
22
tests/Unit/IsValidDomainUrlTest.php
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
it('accepts hostnames containing underscores', function () {
|
||||||
|
// Regression: PHP's FILTER_VALIDATE_URL rejects underscores in the host,
|
||||||
|
// which blocked valid service domains (e.g. Docker service naming) from
|
||||||
|
// being saved and getting Let's Encrypt certificates. See issue #10597.
|
||||||
|
expect(isValidDomainUrl('https://myapp_service.example.com'))->toBeTrue();
|
||||||
|
expect(isValidDomainUrl('http://my_app.example.com'))->toBeTrue();
|
||||||
|
expect(isValidDomainUrl('https://a_b_c.example.com/path'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts ordinary domains and URLs', function () {
|
||||||
|
expect(isValidDomainUrl('https://example.com'))->toBeTrue();
|
||||||
|
expect(isValidDomainUrl('http://sub.example.com:8080/path?q=1'))->toBeTrue();
|
||||||
|
expect(isValidDomainUrl('https://example.com/a_b'))->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects strings that are not valid URLs', function () {
|
||||||
|
expect(isValidDomainUrl('not a url'))->toBeFalse();
|
||||||
|
expect(isValidDomainUrl('example.com'))->toBeFalse();
|
||||||
|
expect(isValidDomainUrl(''))->toBeFalse();
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue