mirror of
https://github.com/coollabsio/coolify.git
synced 2026-08-24 02:24:11 -05:00
fix(applications): normalize nested base64 custom labels
This commit is contained in:
@@ -4456,8 +4456,8 @@ class ApplicationsController extends Controller
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$customLabels = base64_decode($request->custom_labels);
|
||||
if (mb_detect_encoding($customLabels, 'UTF-8', true) === false) {
|
||||
$customLabels = decodeBase64EncodedLabels($request->custom_labels);
|
||||
if ($customLabels === null) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => [
|
||||
@@ -4465,6 +4465,7 @@ class ApplicationsController extends Controller
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
$request->offsetSet('custom_labels', base64_encode($customLabels));
|
||||
}
|
||||
if ($request->has('domains') && $server->isProxyShouldRun()) {
|
||||
$uuid = $request->uuid;
|
||||
|
||||
@@ -2243,18 +2243,18 @@ class Application extends BaseModel
|
||||
|
||||
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||
{
|
||||
$customLabels = data_get($this, 'custom_labels');
|
||||
if (! $customLabels) {
|
||||
$storedLabels = data_get($this, 'custom_labels');
|
||||
if (! $storedLabels) {
|
||||
return;
|
||||
}
|
||||
if (base64_encode(base64_decode($customLabels, true)) !== $customLabels) {
|
||||
$this->custom_labels = str($customLabels)->replace(',', "\n");
|
||||
$this->custom_labels = base64_encode($customLabels);
|
||||
}
|
||||
$customLabels = base64_decode($this->custom_labels);
|
||||
if (mb_detect_encoding($customLabels, 'UTF-8', true) === false) {
|
||||
|
||||
$customLabels = decodeBase64EncodedLabels($storedLabels);
|
||||
if ($customLabels === null && ! isBase64Encoded($storedLabels)) {
|
||||
$customLabels = str($storedLabels)->replace(',', "\n")->value();
|
||||
} elseif ($customLabels === null) {
|
||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($this, $preview)))->replace('|coolify|', "\n");
|
||||
}
|
||||
|
||||
$this->custom_labels = base64_encode($customLabels);
|
||||
$this->save();
|
||||
|
||||
|
||||
@@ -2414,6 +2414,32 @@ function isBase64Encoded($strValue)
|
||||
{
|
||||
return base64_encode(base64_decode($strValue, true)) === $strValue;
|
||||
}
|
||||
|
||||
function decodeBase64EncodedLabels(string $value): ?string
|
||||
{
|
||||
if (! isBase64Encoded($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = base64_decode($value, true);
|
||||
$labels = $decoded;
|
||||
|
||||
while ($decoded !== '' && isBase64Encoded($decoded)) {
|
||||
$decoded = base64_decode($decoded, true);
|
||||
if (mb_detect_encoding($decoded, 'UTF-8', true) !== false) {
|
||||
$lines = preg_split('/\r\n|\n|\r/', $decoded);
|
||||
$containsOnlyLabels = collect($lines)
|
||||
->filter(fn (string $line) => $line !== '')
|
||||
->every(fn (string $line) => str_contains($line, '=') && ! str_starts_with($line, '='));
|
||||
|
||||
if ($containsOnlyLabels) {
|
||||
$labels = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mb_detect_encoding($labels, 'UTF-8', true) === false ? null : $labels;
|
||||
}
|
||||
function customApiValidator(Collection|array $item, array $rules, array $messages = [])
|
||||
{
|
||||
if (is_array($item)) {
|
||||
|
||||
@@ -185,6 +185,26 @@ test('http basic auth updates preserve user-managed labels', function () {
|
||||
expect(base64_decode($this->application->fresh()->custom_labels))->toBe('sentinel-label=true');
|
||||
});
|
||||
|
||||
test('nested base64 custom labels are stored in canonical form', function () {
|
||||
$labels = "traefik.enable=true\ntraefik.http.routers.web.rule=Host(`example.com`)";
|
||||
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
'custom_labels' => base64_encode(base64_encode($labels)),
|
||||
])
|
||||
->assertOk();
|
||||
|
||||
expect($this->application->fresh()->custom_labels)->toBe(base64_encode($labels));
|
||||
});
|
||||
|
||||
test('deployment parsing repairs historically nested custom labels', function () {
|
||||
$labels = "traefik.enable=true\ntraefik.http.routers.web.rule=Host(`example.com`)";
|
||||
$this->application->update(['custom_labels' => base64_encode(base64_encode($labels))]);
|
||||
|
||||
expect($this->application->parseContainerLabels())->toBe($labels)
|
||||
->and($this->application->fresh()->custom_labels)->toBe(base64_encode($labels));
|
||||
});
|
||||
|
||||
test('rejects invalid boolean application settings', function () {
|
||||
$this->withHeaders(applicationSettingsApiHeaders($this->bearerToken))
|
||||
->patchJson("/api/v1/applications/{$this->application->uuid}", [
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
it('decodes nested base64 container labels to label text', function () {
|
||||
$labels = "traefik.enable=true\ntraefik.http.routers.web.rule=Host(`example.com`)";
|
||||
|
||||
expect(decodeBase64EncodedLabels(base64_encode(base64_encode($labels))))->toBe($labels);
|
||||
});
|
||||
|
||||
it('decodes single encoded container labels without changing them', function () {
|
||||
$labels = "traefik.enable=true\ncom.example.version=1";
|
||||
|
||||
expect(decodeBase64EncodedLabels(base64_encode($labels)))->toBe($labels);
|
||||
});
|
||||
|
||||
it('rejects values that are not valid base64', function () {
|
||||
expect(decodeBase64EncodedLabels('traefik.enable=true'))->toBeNull();
|
||||
});
|
||||
|
||||
it('handles an empty encoded value', function () {
|
||||
expect(decodeBase64EncodedLabels(''))->toBe('');
|
||||
});
|
||||
|
||||
it('does not decode label text that happens to be valid base64', function () {
|
||||
expect(decodeBase64EncodedLabels(base64_encode('foo=')))->toBe('foo=');
|
||||
});
|
||||
Reference in New Issue
Block a user