mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
fix(env): keep locked variables locked but allow a new value
A locked variable used by Docker Compose could not be changed, unlocked, or deleted, so a lock by mistake had no fix in the UI. A locked value can now be replaced without showing it. The lock is permanent in the UI, the API, and MCP, and Lock asks for confirmation. Closes #6803 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d07d178f0
commit
da7779d061
14 files changed
+198
-49
No files matched your search
@@ -4038,7 +4038,7 @@ class ApplicationsController extends Controller
|
|||||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -4191,7 +4191,7 @@ class ApplicationsController extends Controller
|
|||||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -4381,7 +4381,7 @@ class ApplicationsController extends Controller
|
|||||||
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3924,7 +3924,7 @@ class DatabasesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -4067,7 +4067,7 @@ class DatabasesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -4199,7 +4199,7 @@ class DatabasesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1490,7 +1490,7 @@ class ServicesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1633,7 +1633,7 @@ class ServicesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1765,7 +1765,7 @@ class ServicesController extends Controller
|
|||||||
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
|
||||||
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
|
||||||
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use OpenApi\Attributes as OA;
|
|||||||
new OA\Property(property: 'value', type: 'string', nullable: true),
|
new OA\Property(property: 'value', type: 'string', nullable: true),
|
||||||
new OA\Property(property: 'is_literal', type: 'boolean'),
|
new OA\Property(property: 'is_literal', type: 'boolean'),
|
||||||
new OA\Property(property: 'is_multiline', type: 'boolean'),
|
new OA\Property(property: 'is_multiline', type: 'boolean'),
|
||||||
new OA\Property(property: 'is_shown_once', type: 'boolean', description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'),
|
new OA\Property(property: 'is_shown_once', type: 'boolean', description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'),
|
||||||
new OA\Property(property: 'comment', type: 'string', maxLength: 256, nullable: true),
|
new OA\Property(property: 'comment', type: 'string', maxLength: 256, nullable: true),
|
||||||
],
|
],
|
||||||
)]
|
)]
|
||||||
@@ -35,7 +35,7 @@ use OpenApi\Attributes as OA;
|
|||||||
new OA\Property(property: 'value', type: 'string', nullable: true),
|
new OA\Property(property: 'value', type: 'string', nullable: true),
|
||||||
new OA\Property(property: 'is_literal', type: 'boolean'),
|
new OA\Property(property: 'is_literal', type: 'boolean'),
|
||||||
new OA\Property(property: 'is_multiline', type: 'boolean'),
|
new OA\Property(property: 'is_multiline', type: 'boolean'),
|
||||||
new OA\Property(property: 'is_shown_once', type: 'boolean', description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'),
|
new OA\Property(property: 'is_shown_once', type: 'boolean', description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'),
|
||||||
new OA\Property(property: 'comment', type: 'string', maxLength: 256, nullable: true),
|
new OA\Property(property: 'comment', type: 'string', maxLength: 256, nullable: true),
|
||||||
],
|
],
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ class Show extends Component
|
|||||||
*/
|
*/
|
||||||
public bool $editorOpen = false;
|
public bool $editorOpen = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A new value for a locked variable. The locked value is never sent to the browser, but it can be replaced.
|
||||||
|
*/
|
||||||
|
public ?string $newLockedValue = null;
|
||||||
|
|
||||||
public array $problematicVariables = [];
|
public array $problematicVariables = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,6 +125,7 @@ class Show extends Component
|
|||||||
'is_runtime' => 'required|boolean',
|
'is_runtime' => 'required|boolean',
|
||||||
'is_buildtime' => 'required|boolean',
|
'is_buildtime' => 'required|boolean',
|
||||||
'real_value' => 'nullable',
|
'real_value' => 'nullable',
|
||||||
|
'newLockedValue' => 'nullable|string',
|
||||||
'is_required' => 'required|boolean',
|
'is_required' => 'required|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -222,6 +228,7 @@ class Show extends Component
|
|||||||
'is_literal' => 'required|boolean',
|
'is_literal' => 'required|boolean',
|
||||||
'is_shown_once' => 'required|boolean',
|
'is_shown_once' => 'required|boolean',
|
||||||
'real_value' => 'nullable',
|
'real_value' => 'nullable',
|
||||||
|
'newLockedValue' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
@@ -235,11 +242,14 @@ class Show extends Component
|
|||||||
if (! $this->env->is_shown_once) {
|
if (! $this->env->is_shown_once) {
|
||||||
$this->env->value = $this->value;
|
$this->env->value = $this->value;
|
||||||
$this->env->is_shown_once = $this->is_shown_once;
|
$this->env->is_shown_once = $this->is_shown_once;
|
||||||
|
} elseif (filled($this->newLockedValue)) {
|
||||||
|
$this->env->value = $this->newLockedValue;
|
||||||
}
|
}
|
||||||
$this->env->comment = $this->comment;
|
$this->env->comment = $this->comment;
|
||||||
$this->env->is_multiline = $this->is_multiline;
|
$this->env->is_multiline = $this->is_multiline;
|
||||||
$this->env->is_literal = $this->is_literal;
|
$this->env->is_literal = $this->is_literal;
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
|
$this->newLockedValue = null;
|
||||||
$this->valuesLoaded = true;
|
$this->valuesLoaded = true;
|
||||||
} else {
|
} else {
|
||||||
// Table metadata only — never decrypt here. Values load via loadValues().
|
// Table metadata only — never decrypt here. Values load via loadValues().
|
||||||
@@ -366,7 +376,9 @@ class Show extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$submittedValue = $this->env->is_shown_once ? $this->env->value : $this->value;
|
$submittedValue = $this->env->is_shown_once
|
||||||
|
? (filled($this->newLockedValue) ? $this->newLockedValue : $this->env->value)
|
||||||
|
: $this->value;
|
||||||
if (! $this->isSharedVariable && $this->is_required && str($submittedValue)->isEmpty()) {
|
if (! $this->isSharedVariable && $this->is_required && str($submittedValue)->isEmpty()) {
|
||||||
if (! $this->env->is_shown_once) {
|
if (! $this->env->is_shown_once) {
|
||||||
$this->value = $this->env->getOriginal('value');
|
$this->value = $this->env->getOriginal('value');
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ use OpenApi\Attributes as OA;
|
|||||||
'is_runtime' => ['type' => 'boolean'],
|
'is_runtime' => ['type' => 'boolean'],
|
||||||
'is_buildtime' => ['type' => 'boolean'],
|
'is_buildtime' => ['type' => 'boolean'],
|
||||||
'is_shared' => ['type' => 'boolean'],
|
'is_shared' => ['type' => 'boolean'],
|
||||||
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'],
|
'is_shown_once' => ['type' => 'boolean', 'description' => 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'],
|
||||||
'key' => ['type' => 'string'],
|
'key' => ['type' => 'string'],
|
||||||
'value' => ['type' => 'string'],
|
'value' => ['type' => 'string'],
|
||||||
'real_value' => ['type' => 'string'],
|
'real_value' => ['type' => 'string'],
|
||||||
@@ -173,6 +173,10 @@ class EnvironmentVariable extends BaseModel
|
|||||||
});
|
});
|
||||||
|
|
||||||
static::saving(function (ModelsEnvironmentVariable $environmentVariable) {
|
static::saving(function (ModelsEnvironmentVariable $environmentVariable) {
|
||||||
|
// A lock is permanent: no UI, API, or MCP path can unlock a variable and show its value again.
|
||||||
|
if ($environmentVariable->exists && $environmentVariable->getOriginal('is_shown_once')) {
|
||||||
|
$environmentVariable->is_shown_once = true;
|
||||||
|
}
|
||||||
if ($environmentVariable->preserve_compose_null && $environmentVariable->isDirty('value') && $environmentVariable->value !== null) {
|
if ($environmentVariable->preserve_compose_null && $environmentVariable->isDirty('value') && $environmentVariable->value !== null) {
|
||||||
$environmentVariable->preserve_compose_null = false;
|
$environmentVariable->preserve_compose_null = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,16 @@ class SharedEnvironmentVariable extends Model
|
|||||||
'is_literal' => 'boolean',
|
'is_literal' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
// A lock is permanent: no UI, API, or MCP path can unlock a variable and show its value again.
|
||||||
|
static::saving(function (SharedEnvironmentVariable $sharedVariable) {
|
||||||
|
if ($sharedVariable->exists && $sharedVariable->getOriginal('is_shown_once')) {
|
||||||
|
$sharedVariable->is_shown_once = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope shared environment variables to a team (API token team_id).
|
* Scope shared environment variables to a team (API token team_id).
|
||||||
*/
|
*/
|
||||||
|
|||||||
+12
-12
@@ -4594,7 +4594,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -4686,7 +4686,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -4777,7 +4777,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -9816,7 +9816,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -9907,7 +9907,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -9997,7 +9997,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -22498,7 +22498,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -22589,7 +22589,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -22679,7 +22679,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@@ -26558,7 +26558,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.",
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
@@ -26590,7 +26590,7 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.",
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"comment": {
|
"comment": {
|
||||||
@@ -27535,7 +27535,7 @@
|
|||||||
},
|
},
|
||||||
"is_shown_once": {
|
"is_shown_once": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values."
|
"description": "If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values."
|
||||||
},
|
},
|
||||||
"key": {
|
"key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
+12
-12
@@ -3062,7 +3062,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -3123,7 +3123,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -3167,7 +3167,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: array
|
type: array
|
||||||
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.' } }, type: object }
|
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.' } }, type: object }
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -6452,7 +6452,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -6512,7 +6512,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -6558,7 +6558,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: array
|
type: array
|
||||||
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.' } }, type: object }
|
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.' } }, type: object }
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -14186,7 +14186,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -14246,7 +14246,7 @@ paths:
|
|||||||
description: 'The flag to indicate if the environment variable is multiline.'
|
description: 'The flag to indicate if the environment variable is multiline.'
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -14292,7 +14292,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
type: array
|
type: array
|
||||||
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.' } }, type: object }
|
items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.' } }, type: object }
|
||||||
type: object
|
type: object
|
||||||
responses:
|
responses:
|
||||||
'201':
|
'201':
|
||||||
@@ -16876,7 +16876,7 @@ components:
|
|||||||
is_multiline:
|
is_multiline:
|
||||||
type: boolean
|
type: boolean
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: boolean
|
type: boolean
|
||||||
comment:
|
comment:
|
||||||
type:
|
type:
|
||||||
@@ -16898,7 +16898,7 @@ components:
|
|||||||
is_multiline:
|
is_multiline:
|
||||||
type: boolean
|
type: boolean
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
type: boolean
|
type: boolean
|
||||||
comment:
|
comment:
|
||||||
type:
|
type:
|
||||||
@@ -17603,7 +17603,7 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
is_shown_once:
|
is_shown_once:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'If true, the saved value is hidden in the UI and API responses. MCP never returns environment variable values.'
|
description: 'If true, the saved value is hidden in the UI and API responses. A lock is permanent: false does not unlock a locked variable, but a new value can still replace the locked one. MCP never returns environment variable values.'
|
||||||
key:
|
key:
|
||||||
type: string
|
type: string
|
||||||
value:
|
value:
|
||||||
|
|||||||
@@ -126,11 +126,10 @@
|
|||||||
class="input w-full italic !text-neutral-500 dark:!text-neutral-500" />
|
class="input w-full italic !text-neutral-500 dark:!text-neutral-500" />
|
||||||
</div>
|
</div>
|
||||||
@elseif ($isLocked)
|
@elseif ($isLocked)
|
||||||
<div class="w-full">
|
<x-forms.input id="newLockedValue" type="password" label="Value" :copyable="false"
|
||||||
<label class="mb-1 flex items-center gap-1 text-sm font-medium">Value</label>
|
placeholder="Hidden after locking. Enter a new value to replace it."
|
||||||
<input disabled type="text" value="Hidden after locking"
|
helper="The locked value cannot be shown. Leave this empty to keep it."
|
||||||
class="input w-full italic !text-neutral-500 dark:!text-neutral-500" />
|
:disabled="!$canUpdate || $isDisabled" />
|
||||||
</div>
|
|
||||||
@else
|
@else
|
||||||
{{-- Multiline: same shell while loading/loaded to avoid layout jump. --}}
|
{{-- Multiline: same shell while loading/loaded to avoid layout jump. --}}
|
||||||
<div x-show="isMultiline" x-cloak wire:key="env-show-value-multiline-{{ $env->id }}"
|
<div x-show="isMultiline" x-cloak wire:key="env-show-value-multiline-{{ $env->id }}"
|
||||||
@@ -283,7 +282,12 @@
|
|||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($canUpdate && !$isLocked && !$isMagicVariable)
|
@if ($canUpdate && !$isLocked && !$isMagicVariable)
|
||||||
<x-forms.button type="button" wire:click="lock">Lock</x-forms.button>
|
<div wire:key="env-lock-action-{{ $env->id }}">
|
||||||
|
<x-modal-confirmation title="Lock Environment Variable?" buttonTitle="Lock"
|
||||||
|
submitAction="lock"
|
||||||
|
:actions="['The value will be hidden. You cannot see or copy it again.', 'You can still replace the value with a new one, but you cannot unlock the variable.']"
|
||||||
|
:confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Lock" />
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($canUpdate && !$isDisabled)
|
@if ($canUpdate && !$isDisabled)
|
||||||
<fieldset disabled wire:dirty.attr.remove="disabled">
|
<fieldset disabled wire:dirty.attr.remove="disabled">
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ describe('application env PATCH keeps omitted flags', function () {
|
|||||||
->and((bool) $env->is_shown_once)->toBeTrue();
|
->and((bool) $env->is_shown_once)->toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('explicit flags are still applied', function () {
|
test('explicit flags are still applied, but a locked variable stays locked', function () {
|
||||||
$this->withHeaders($this->headers)
|
$this->withHeaders($this->headers)
|
||||||
->patchJson("/api/v1/applications/{$this->application->uuid}/envs", [
|
->patchJson("/api/v1/applications/{$this->application->uuid}/envs", [
|
||||||
'key' => 'FLAGGED_KEY',
|
'key' => 'FLAGGED_KEY',
|
||||||
@@ -427,7 +427,7 @@ describe('application env PATCH keeps omitted flags', function () {
|
|||||||
$env = $this->env->fresh();
|
$env = $this->env->fresh();
|
||||||
expect((bool) $env->is_literal)->toBeFalse()
|
expect((bool) $env->is_literal)->toBeFalse()
|
||||||
->and($env->is_multiline)->toBeFalse()
|
->and($env->is_multiline)->toBeFalse()
|
||||||
->and((bool) $env->is_shown_once)->toBeFalse();
|
->and((bool) $env->is_shown_once)->toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -645,6 +645,60 @@ describe('env PATCH keeps the value when value is omitted', function () {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('env API cannot unlock a locked variable', function () {
|
||||||
|
test('is_shown_once false keeps the lock and the value can still be replaced', function (string $resourceType, string $endpoint, bool $bulk) {
|
||||||
|
$resource = match ($resourceType) {
|
||||||
|
'application' => Application::factory()->create([
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
]),
|
||||||
|
'service' => Service::factory()->create([
|
||||||
|
'server_id' => $this->server->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
]),
|
||||||
|
'database' => StandalonePostgresql::create([
|
||||||
|
'name' => 'db',
|
||||||
|
'postgres_user' => 'postgres',
|
||||||
|
'postgres_password' => 'password',
|
||||||
|
'postgres_db' => 'app',
|
||||||
|
'image' => 'postgres:16',
|
||||||
|
'environment_id' => $this->environment->id,
|
||||||
|
'destination_id' => $this->destination->id,
|
||||||
|
'destination_type' => $this->destination->getMorphClass(),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
$env = EnvironmentVariable::create([
|
||||||
|
'key' => 'LOCKED_KEY',
|
||||||
|
'value' => 'locked-secret',
|
||||||
|
'is_shown_once' => true,
|
||||||
|
'resourceable_type' => $resource->getMorphClass(),
|
||||||
|
'resourceable_id' => $resource->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$payload = ['key' => 'LOCKED_KEY', 'value' => 'rotated-secret', 'is_shown_once' => false];
|
||||||
|
$this->withHeaders(['Authorization' => 'Bearer '.$this->bearerToken, 'Content-Type' => 'application/json'])
|
||||||
|
->patchJson("/api/v1/{$endpoint}/{$resource->uuid}/envs".($bulk ? '/bulk' : ''), $bulk ? ['data' => [$payload]] : $payload)
|
||||||
|
->assertStatus(201)
|
||||||
|
->assertDontSee('rotated-secret');
|
||||||
|
|
||||||
|
$env->refresh();
|
||||||
|
expect((bool) $env->is_shown_once)->toBeTrue()
|
||||||
|
->and($env->value)->toBe('rotated-secret');
|
||||||
|
})->with([
|
||||||
|
'application single' => ['application', 'applications', false],
|
||||||
|
'application bulk' => ['application', 'applications', true],
|
||||||
|
'service single' => ['service', 'services', false],
|
||||||
|
'service bulk' => ['service', 'services', true],
|
||||||
|
'database single' => ['database', 'databases', false],
|
||||||
|
'database bulk' => ['database', 'databases', true],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
describe('env API keeps multiline values exact', function () {
|
describe('env API keeps multiline values exact', function () {
|
||||||
test('create and bulk update keep the trailing newline of a private key', function () {
|
test('create and bulk update keep the trailing newline of a private key', function () {
|
||||||
$application = Application::factory()->create([
|
$application = Application::factory()->create([
|
||||||
|
|||||||
@@ -85,6 +85,23 @@ describe('Team shared environment variables API', function () {
|
|||||||
expect(SharedEnvironmentVariable::query()->whereKey($envId)->exists())->toBeFalse();
|
expect(SharedEnvironmentVariable::query()->whereKey($envId)->exists())->toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('cannot unlock a locked team shared env', function () {
|
||||||
|
$env = SharedEnvironmentVariable::create([
|
||||||
|
'key' => 'LOCKED_TEAM_SECRET',
|
||||||
|
'value' => 'locked-secret',
|
||||||
|
'is_shown_once' => true,
|
||||||
|
'type' => 'team',
|
||||||
|
'team_id' => $this->team->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->withHeaders(sharedEnvHeaders($this->bearerToken))
|
||||||
|
->patchJson("/api/v1/team/envs/{$env->id}", ['is_shown_once' => false])
|
||||||
|
->assertOk()
|
||||||
|
->assertDontSee('locked-secret');
|
||||||
|
|
||||||
|
expect((bool) $env->fresh()->is_shown_once)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
test('hides value without can_read_sensitive', function () {
|
test('hides value without can_read_sensitive', function () {
|
||||||
SharedEnvironmentVariable::create([
|
SharedEnvironmentVariable::create([
|
||||||
'key' => 'HIDDEN_SECRET',
|
'key' => 'HIDDEN_SECRET',
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ beforeEach(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('add suggests shared variables of the resource team when the session team differs', function () {
|
test('add suggests shared variables of the resource team when the session team differs', function () {
|
||||||
$component = Livewire::test(Add::class, ['resource' => $this->application])
|
$component = Livewire::test(Add::class, ['resource' => $this->application]);
|
||||||
->set('parameters', $this->parameters);
|
$component->instance()->parameters = $this->parameters;
|
||||||
|
|
||||||
$suggestions = $component->instance()->availableSharedVariables();
|
$suggestions = $component->instance()->availableSharedVariables();
|
||||||
|
|
||||||
@@ -60,8 +60,8 @@ test('show suggests shared variables of the resource team when the session team
|
|||||||
'resourceable_id' => $this->application->id,
|
'resourceable_id' => $this->application->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$component = Livewire::test(Show::class, ['env' => $env, 'type' => 'application'])
|
$component = Livewire::test(Show::class, ['env' => $env, 'type' => 'application']);
|
||||||
->set('parameters', $this->parameters);
|
$component->instance()->parameters = $this->parameters;
|
||||||
|
|
||||||
$suggestions = $component->instance()->availableSharedVariables();
|
$suggestions = $component->instance()->availableSharedVariables();
|
||||||
|
|
||||||
|
|||||||
@@ -116,3 +116,51 @@ test('changing the comment of a locked shared variable keeps its value', functio
|
|||||||
expect($env->value)->toBe('locked-shared-secret')
|
expect($env->value)->toBe('locked-shared-secret')
|
||||||
->and($env->comment)->toBe('rotated monthly');
|
->and($env->comment)->toBe('rotated monthly');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a locked variable can get a new value and stays locked', function () {
|
||||||
|
$env = createLockedVariableForUpdateTest();
|
||||||
|
|
||||||
|
Livewire::test(Show::class, ['env' => $env, 'type' => 'application'])
|
||||||
|
->call('loadValues')
|
||||||
|
->set('newLockedValue', 'rotated-secret')
|
||||||
|
->call('submit')
|
||||||
|
->assertDispatched('success')
|
||||||
|
->assertSet('newLockedValue', null)
|
||||||
|
->assertSet('value', null);
|
||||||
|
|
||||||
|
$env->refresh();
|
||||||
|
expect($env->value)->toBe('rotated-secret')
|
||||||
|
->and((bool) $env->is_shown_once)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a locked variable that Docker Compose uses can get a new value', function () {
|
||||||
|
$this->application->update([
|
||||||
|
'build_pack' => 'dockercompose',
|
||||||
|
'docker_compose_raw' => "services:\n app:\n image: nginx\n environment:\n - API_TOKEN=\${API_TOKEN}\n",
|
||||||
|
]);
|
||||||
|
$env = createLockedVariableForUpdateTest(required: true, value: null);
|
||||||
|
|
||||||
|
Livewire::test(Show::class, ['env' => $env, 'type' => 'application'])
|
||||||
|
->call('loadValues')
|
||||||
|
->set('newLockedValue', 'rotated-secret')
|
||||||
|
->call('submit')
|
||||||
|
->assertNotDispatched('error')
|
||||||
|
->assertDispatched('success');
|
||||||
|
|
||||||
|
$env->refresh();
|
||||||
|
expect($env->value)->toBe('rotated-secret')
|
||||||
|
->and((bool) $env->is_shown_once)->toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a member cannot replace a locked value', function () {
|
||||||
|
$env = createLockedVariableForUpdateTest();
|
||||||
|
$member = User::factory()->create();
|
||||||
|
$this->team->members()->attach($member, ['role' => 'member']);
|
||||||
|
$this->actingAs($member);
|
||||||
|
|
||||||
|
expect(fn () => Livewire::test(Show::class, ['env' => $env, 'type' => 'application'])
|
||||||
|
->set('newLockedValue', 'attacker')
|
||||||
|
->call('submit'))->toThrow(Exception::class, 'This action is unauthorized.');
|
||||||
|
|
||||||
|
expect($env->refresh()->value)->toBe('locked-secret');
|
||||||
|
});
|
||||||
Reference in new issue
Block a user