diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index ad1f50ea2..82d662177 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -1158,7 +1158,7 @@ private function create_application(Request $request, $type)
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
- $application->fill($request->all());
+ $application->fill($request->only($allowedFields));
$dockerComposeDomainsJson = collect();
if ($request->has('docker_compose_domains')) {
$dockerComposeDomains = collect($request->docker_compose_domains);
@@ -1385,7 +1385,7 @@ private function create_application(Request $request, $type)
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
- $application->fill($request->all());
+ $application->fill($request->only($allowedFields));
$dockerComposeDomainsJson = collect();
if ($request->has('docker_compose_domains')) {
@@ -1585,7 +1585,7 @@ private function create_application(Request $request, $type)
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
- $application->fill($request->all());
+ $application->fill($request->only($allowedFields));
$dockerComposeDomainsJson = collect();
if ($request->has('docker_compose_domains')) {
@@ -1772,7 +1772,7 @@ private function create_application(Request $request, $type)
}
$application = new Application;
- $application->fill($request->all());
+ $application->fill($request->only($allowedFields));
$application->fqdn = $fqdn;
$application->ports_exposes = $port;
$application->build_pack = 'dockerfile';
@@ -1884,7 +1884,7 @@ private function create_application(Request $request, $type)
$application = new Application;
removeUnnecessaryFieldsFromRequest($request);
- $application->fill($request->all());
+ $application->fill($request->only($allowedFields));
$application->fqdn = $fqdn;
$application->build_pack = 'dockerimage';
$application->destination_id = $destination->id;
@@ -2000,7 +2000,7 @@ private function create_application(Request $request, $type)
$service = new Service;
removeUnnecessaryFieldsFromRequest($request);
- $service->fill($request->all());
+ $service->fill($request->only($allowedFields));
$service->docker_compose_raw = $dockerComposeRaw;
$service->environment_id = $environment->id;
@@ -2760,7 +2760,7 @@ public function update_by_uuid(Request $request)
removeUnnecessaryFieldsFromRequest($request);
- $data = $request->all();
+ $data = $request->only($allowedFields);
if ($requestHasDomains && $server->isProxyShouldRun()) {
data_set($data, 'fqdn', $domains);
}
diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php
index 660ed4529..3fd1b8db8 100644
--- a/app/Http/Controllers/Api/DatabasesController.php
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -1740,7 +1740,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('postgres_conf', $postgresConf);
}
- $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -1795,7 +1795,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('mariadb_conf', $mariadbConf);
}
- $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -1854,7 +1854,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('mysql_conf', $mysqlConf);
}
- $database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_mysql($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -1910,7 +1910,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('redis_conf', $redisConf);
}
- $database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_redis($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -1947,7 +1947,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
removeUnnecessaryFieldsFromRequest($request);
- $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -1996,7 +1996,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('keydb_conf', $keydbConf);
}
- $database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_keydb($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -2032,7 +2032,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
], 422);
}
removeUnnecessaryFieldsFromRequest($request);
- $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
@@ -2090,7 +2090,7 @@ public function create_database(Request $request, NewDatabaseTypes $type)
}
$request->offsetSet('mongo_conf', $mongoConf);
}
- $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
+ $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->only($allowedFields));
if ($instantDeploy) {
StartDatabase::dispatch($database);
}
diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php
index e7b36cb9a..2c62928c2 100644
--- a/app/Http/Controllers/Api/SecurityController.php
+++ b/app/Http/Controllers/Api/SecurityController.php
@@ -4,6 +4,7 @@
use App\Http\Controllers\Controller;
use App\Models\PrivateKey;
+use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use OpenApi\Attributes as OA;
@@ -176,7 +177,7 @@ public function create_key(Request $request)
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
- if ($return instanceof \Illuminate\Http\JsonResponse) {
+ if ($return instanceof JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
@@ -300,7 +301,7 @@ public function update_key(Request $request)
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
- if ($return instanceof \Illuminate\Http\JsonResponse) {
+ if ($return instanceof JsonResponse) {
return $return;
}
@@ -330,7 +331,7 @@ public function update_key(Request $request)
'message' => 'Private Key not found.',
], 404);
}
- $foundKey->update($request->all());
+ $foundKey->update($request->only($allowedFields));
return response()->json(serializeApiResponse([
'uuid' => $foundKey->uuid,
diff --git a/app/Livewire/Project/CloneMe.php b/app/Livewire/Project/CloneMe.php
index 3b3e42619..3b04c3b7f 100644
--- a/app/Livewire/Project/CloneMe.php
+++ b/app/Livewire/Project/CloneMe.php
@@ -139,7 +139,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'status' => 'exited',
'started_at' => null,
@@ -187,7 +187,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $newDatabase->id,
]);
@@ -216,7 +216,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resource_id' => $newDatabase->id,
]);
$newStorage->save();
@@ -229,7 +229,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'database_id' => $newDatabase->id,
'database_type' => $newDatabase->getMorphClass(),
@@ -247,7 +247,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill($payload);
+ ])->forceFill($payload);
$newEnvironmentVariable->save();
}
}
@@ -258,7 +258,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'environment_id' => $environment->id,
'destination_id' => $this->selectedDestination,
@@ -276,7 +276,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => (string) new Cuid2,
'service_id' => $newService->id,
'team_id' => currentTeam()->id,
@@ -290,7 +290,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resourceable_id' => $newService->id,
'resourceable_type' => $newService->getMorphClass(),
]);
@@ -298,9 +298,9 @@ public function clone(string $type)
}
foreach ($newService->applications() as $application) {
- $application->update([
+ $application->forceFill([
'status' => 'exited',
- ]);
+ ])->save();
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -315,7 +315,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $application->id,
]);
@@ -344,7 +344,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resource_id' => $application->id,
]);
$newStorage->save();
@@ -352,9 +352,9 @@ public function clone(string $type)
}
foreach ($newService->databases() as $database) {
- $database->update([
+ $database->forceFill([
'status' => 'exited',
- ]);
+ ])->save();
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -369,7 +369,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $database->id,
]);
@@ -398,7 +398,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resource_id' => $database->id,
]);
$newStorage->save();
@@ -411,7 +411,7 @@ public function clone(string $type)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'database_id' => $database->id,
'database_type' => $database->getMorphClass(),
diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php
index e769e4bcb..a26b43026 100644
--- a/app/Livewire/Project/Shared/ResourceOperations.php
+++ b/app/Livewire/Project/Shared/ResourceOperations.php
@@ -7,9 +7,18 @@
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Jobs\VolumeCloneJob;
+use App\Models\Application;
use App\Models\Environment;
use App\Models\Project;
+use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDocker;
+use App\Models\StandaloneDragonfly;
+use App\Models\StandaloneKeydb;
+use App\Models\StandaloneMariadb;
+use App\Models\StandaloneMongodb;
+use App\Models\StandaloneMysql;
+use App\Models\StandalonePostgresql;
+use App\Models\StandaloneRedis;
use App\Models\SwarmDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -60,7 +69,7 @@ public function cloneTo($destination_id)
$uuid = (string) new Cuid2;
$server = $new_destination->server;
- if ($this->resource->getMorphClass() === \App\Models\Application::class) {
+ if ($this->resource->getMorphClass() === Application::class) {
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$route = route('project.application.configuration', [
@@ -71,21 +80,21 @@ public function cloneTo($destination_id)
return redirect()->to($route);
} elseif (
- $this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
- $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
+ $this->resource->getMorphClass() === StandalonePostgresql::class ||
+ $this->resource->getMorphClass() === StandaloneMongodb::class ||
+ $this->resource->getMorphClass() === StandaloneMysql::class ||
+ $this->resource->getMorphClass() === StandaloneMariadb::class ||
+ $this->resource->getMorphClass() === StandaloneRedis::class ||
+ $this->resource->getMorphClass() === StandaloneKeydb::class ||
+ $this->resource->getMorphClass() === StandaloneDragonfly::class ||
+ $this->resource->getMorphClass() === StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'status' => 'exited',
@@ -133,7 +142,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $new_resource->id,
]);
@@ -162,7 +171,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resource_id' => $new_resource->id,
]);
$newStorage->save();
@@ -175,7 +184,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'database_id' => $new_resource->id,
'database_type' => $new_resource->getMorphClass(),
@@ -194,7 +203,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill($payload);
+ ])->forceFill($payload);
$newEnvironmentVariable->save();
}
@@ -211,7 +220,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id,
@@ -232,7 +241,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'uuid' => (string) new Cuid2,
'service_id' => $new_resource->id,
'team_id' => currentTeam()->id,
@@ -246,7 +255,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
]);
@@ -254,9 +263,9 @@ public function cloneTo($destination_id)
}
foreach ($new_resource->applications() as $application) {
- $application->update([
+ $application->forceFill([
'status' => 'exited',
- ]);
+ ])->save();
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -271,7 +280,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $application->id,
]);
@@ -296,9 +305,9 @@ public function cloneTo($destination_id)
}
foreach ($new_resource->databases() as $database) {
- $database->update([
+ $database->forceFill([
'status' => 'exited',
- ]);
+ ])->save();
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -313,7 +322,7 @@ public function cloneTo($destination_id)
'id',
'created_at',
'updated_at',
- ])->fill([
+ ])->forceFill([
'name' => $newName,
'resource_id' => $database->id,
]);
@@ -354,9 +363,9 @@ public function moveTo($environment_id)
try {
$this->authorize('update', $this->resource);
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
- $this->resource->update([
+ $this->resource->forceFill([
'environment_id' => $environment_id,
- ]);
+ ])->save();
if ($this->resource->type() === 'application') {
$route = route('project.application.configuration', [
'project_uuid' => $new_environment->project->uuid,
diff --git a/app/Models/Application.php b/app/Models/Application.php
index c446052b3..a4789ae4a 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -118,7 +118,92 @@ class Application extends BaseModel
private static $parserVersion = '5';
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'fqdn',
+ 'git_repository',
+ 'git_branch',
+ 'git_commit_sha',
+ 'git_full_url',
+ 'docker_registry_image_name',
+ 'docker_registry_image_tag',
+ 'build_pack',
+ 'static_image',
+ 'install_command',
+ 'build_command',
+ 'start_command',
+ 'ports_exposes',
+ 'ports_mappings',
+ 'base_directory',
+ 'publish_directory',
+ 'health_check_enabled',
+ 'health_check_path',
+ 'health_check_port',
+ 'health_check_host',
+ 'health_check_method',
+ 'health_check_return_code',
+ 'health_check_scheme',
+ 'health_check_response_text',
+ 'health_check_interval',
+ 'health_check_timeout',
+ 'health_check_retries',
+ 'health_check_start_period',
+ 'health_check_type',
+ 'health_check_command',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'status',
+ 'preview_url_template',
+ 'dockerfile',
+ 'dockerfile_location',
+ 'dockerfile_target_build',
+ 'custom_labels',
+ 'custom_docker_run_options',
+ 'post_deployment_command',
+ 'post_deployment_command_container',
+ 'pre_deployment_command',
+ 'pre_deployment_command_container',
+ 'manual_webhook_secret_github',
+ 'manual_webhook_secret_gitlab',
+ 'manual_webhook_secret_bitbucket',
+ 'manual_webhook_secret_gitea',
+ 'docker_compose_location',
+ 'docker_compose_pr_location',
+ 'docker_compose',
+ 'docker_compose_pr',
+ 'docker_compose_raw',
+ 'docker_compose_pr_raw',
+ 'docker_compose_domains',
+ 'docker_compose_custom_start_command',
+ 'docker_compose_custom_build_command',
+ 'swarm_replicas',
+ 'swarm_placement_constraints',
+ 'watch_paths',
+ 'redirect',
+ 'compose_parsing_version',
+ 'custom_nginx_configuration',
+ 'custom_network_aliases',
+ 'custom_healthcheck_found',
+ 'nixpkgsarchive',
+ 'is_http_basic_auth_enabled',
+ 'http_basic_auth_username',
+ 'http_basic_auth_password',
+ 'connect_to_docker_network',
+ 'force_domain_override',
+ 'is_container_label_escape_enabled',
+ 'use_build_server',
+ 'config_hash',
+ 'last_online_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ ];
protected $appends = ['server_status'];
@@ -1145,7 +1230,7 @@ public function getGitRemoteStatus(string $deployment_uuid)
'is_accessible' => true,
'error' => null,
];
- } catch (\RuntimeException $ex) {
+ } catch (RuntimeException $ex) {
return [
'is_accessible' => false,
'error' => $ex->getMessage(),
@@ -1202,7 +1287,7 @@ public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_
];
}
- if ($this->source->getMorphClass() === \App\Models\GitlabApp::class) {
+ if ($this->source->getMorphClass() === GitlabApp::class) {
$gitlabSource = $this->source;
$private_key = data_get($gitlabSource, 'privateKey.private_key');
@@ -1354,7 +1439,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
$source_html_url_host = $url['host'];
$source_html_url_scheme = $url['scheme'];
- if ($this->source->getMorphClass() === \App\Models\GithubApp::class) {
+ if ($this->source->getMorphClass() === GithubApp::class) {
if ($this->source->is_public) {
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
$escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}");
@@ -1409,7 +1494,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
];
}
- if ($this->source->getMorphClass() === \App\Models\GitlabApp::class) {
+ if ($this->source->getMorphClass() === GitlabApp::class) {
$gitlabSource = $this->source;
$private_key = data_get($gitlabSource, 'privateKey.private_key');
@@ -1600,7 +1685,7 @@ public function oldRawParser()
try {
$yaml = Yaml::parse($this->docker_compose_raw);
} catch (\Exception $e) {
- throw new \RuntimeException($e->getMessage());
+ throw new RuntimeException($e->getMessage());
}
$services = data_get($yaml, 'services');
@@ -1682,7 +1767,7 @@ public function loadComposeFile($isInit = false, ?string $restoreBaseDirectory =
$fileList = collect([".$workdir$composeFile"]);
$gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid);
if (! $gitRemoteStatus['is_accessible']) {
- throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
+ throw new RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}");
}
$getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false);
$gitVersion = str($getGitVersion)->explode(' ')->last();
@@ -1732,15 +1817,15 @@ public function loadComposeFile($isInit = false, ?string $restoreBaseDirectory =
$this->save();
if (str($e->getMessage())->contains('No such file')) {
- throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})
Check if you used the right extension (.yaml or .yml) in the compose file name.");
+ throw new RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})
Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
if (str($e->getMessage())->contains('fatal: repository') && str($e->getMessage())->contains('does not exist')) {
if ($this->deploymentType() === 'deploy_key') {
- throw new \RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
+ throw new RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
}
- throw new \RuntimeException('Repository does not exist. Please check your repository URL and try again.');
+ throw new RuntimeException('Repository does not exist. Please check your repository URL and try again.');
}
- throw new \RuntimeException($e->getMessage());
+ throw new RuntimeException($e->getMessage());
} finally {
// Cleanup only - restoration happens in catch block
$commands = collect([
@@ -1793,7 +1878,7 @@ public function loadComposeFile($isInit = false, ?string $restoreBaseDirectory =
$this->base_directory = $initialBaseDirectory;
$this->save();
- throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})
Check if you used the right extension (.yaml or .yml) in the compose file name.");
+ throw new RuntimeException("Docker Compose file not found at: $workdir$composeFile (branch: {$this->git_branch})
Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
}
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 9237763c8..b3dcf6353 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -265,8 +265,6 @@ public static function flushIdentityMap(): void
'server_metadata',
];
- protected $guarded = [];
-
use HasSafeStringAttribute;
public function type()
diff --git a/app/Models/Service.php b/app/Models/Service.php
index 84c047bb7..b3ff85e53 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -15,6 +15,7 @@
use OpenApi\Attributes as OA;
use Spatie\Activitylog\Models\Activity;
use Spatie\Url\Url;
+use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
#[OA\Schema(
@@ -47,7 +48,16 @@ class Service extends BaseModel
private static $parserVersion = '5';
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'docker_compose_raw',
+ 'docker_compose',
+ 'connect_to_docker_network',
+ 'service_type',
+ 'config_hash',
+ 'compose_parsing_version',
+ ];
protected $appends = ['server_status', 'status'];
@@ -1552,7 +1562,7 @@ public function saveComposeConfigs()
// Generate SERVICE_NAME_* environment variables from docker-compose services
if ($this->docker_compose) {
try {
- $dockerCompose = \Symfony\Component\Yaml\Yaml::parse($this->docker_compose);
+ $dockerCompose = Yaml::parse($this->docker_compose);
$services = data_get($dockerCompose, 'services', []);
foreach ($services as $serviceName => $_) {
$envs->push('SERVICE_NAME_'.str($serviceName)->replace('-', '_')->replace('.', '_')->upper().'='.$serviceName);
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index 143aadb6a..74382d87c 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -13,7 +13,31 @@ class StandaloneClickhouse extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'clickhouse_admin_user',
+ 'clickhouse_admin_password',
+ 'is_log_drain_enabled',
+ 'is_include_timestamps',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index c823c305b..7cc74f0ce 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -13,7 +13,30 @@ class StandaloneDragonfly extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'dragonfly_password',
+ 'is_log_drain_enabled',
+ 'is_include_timestamps',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index f286e8538..7a0d7f03d 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -13,7 +13,31 @@ class StandaloneKeydb extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'keydb_password',
+ 'keydb_conf',
+ 'is_log_drain_enabled',
+ 'is_include_timestamps',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index efa62353c..6cac9e5f4 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -14,7 +14,32 @@ class StandaloneMariadb extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'mariadb_root_password',
+ 'mariadb_user',
+ 'mariadb_password',
+ 'mariadb_database',
+ 'mariadb_conf',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index 9418ebc21..5ca4ef5d3 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -13,7 +13,31 @@ class StandaloneMongodb extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'mongo_conf',
+ 'mongo_initdb_root_username',
+ 'mongo_initdb_root_password',
+ 'mongo_initdb_database',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index 2b7e9f2b6..cf8d78a9c 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -13,7 +13,32 @@ class StandaloneMysql extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'mysql_root_password',
+ 'mysql_user',
+ 'mysql_password',
+ 'mysql_database',
+ 'mysql_conf',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index cea600236..7db334c5d 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -13,7 +13,34 @@ class StandalonePostgresql extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'postgres_user',
+ 'postgres_password',
+ 'postgres_db',
+ 'postgres_initdb_args',
+ 'postgres_host_auth_method',
+ 'postgres_conf',
+ 'init_scripts',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index 0e904ab31..812a0e5cb 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -13,7 +13,29 @@ class StandaloneRedis extends BaseModel
{
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'redis_password',
+ 'redis_conf',
+ 'status',
+ 'image',
+ 'is_public',
+ 'public_port',
+ 'ports_mappings',
+ 'limits_memory',
+ 'limits_memory_swap',
+ 'limits_memory_swappiness',
+ 'limits_memory_reservation',
+ 'limits_cpus',
+ 'limits_cpuset',
+ 'limits_cpu_shares',
+ 'started_at',
+ 'restart_count',
+ 'last_restart_at',
+ 'last_restart_type',
+ 'last_online_at',
+ ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
diff --git a/app/Models/Team.php b/app/Models/Team.php
index 5a7b377b6..4b9751706 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -40,7 +40,14 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
{
use HasFactory, HasNotificationSettings, HasSafeStringAttribute, Notifiable;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'description',
+ 'show_boarding',
+ 'custom_server_limit',
+ 'use_instance_email_settings',
+ 'resend_api_key',
+ ];
protected $casts = [
'personal_team' => 'boolean',
diff --git a/app/Models/User.php b/app/Models/User.php
index 4561cddb2..6b6f93239 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -4,7 +4,9 @@
use App\Jobs\UpdateStripeCustomerEmailJob;
use App\Notifications\Channels\SendsEmail;
+use App\Notifications\TransactionalEmails\EmailChangeVerification;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
+use App\Services\ChangelogService;
use App\Traits\DeletesUserSessions;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -41,7 +43,16 @@ class User extends Authenticatable implements SendsEmail
{
use DeletesUserSessions, HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
- protected $guarded = [];
+ protected $fillable = [
+ 'name',
+ 'email',
+ 'password',
+ 'force_password_reset',
+ 'marketing_emails',
+ 'pending_email',
+ 'email_change_code',
+ 'email_change_code_expires_at',
+ ];
protected $hidden = [
'password',
@@ -228,7 +239,7 @@ public function changelogReads()
public function getUnreadChangelogCount(): int
{
- return app(\App\Services\ChangelogService::class)->getUnreadCountForUser($this);
+ return app(ChangelogService::class)->getUnreadCountForUser($this);
}
public function getRecipients(): array
@@ -239,7 +250,7 @@ public function getRecipients(): array
public function sendVerificationEmail()
{
$mail = new MailMessage;
- $url = Url::temporarySignedRoute(
+ $url = URL::temporarySignedRoute(
'verify.verify',
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
[
@@ -408,7 +419,7 @@ public function requestEmailChange(string $newEmail): void
]);
// Send verification email to new address
- $this->notify(new \App\Notifications\TransactionalEmails\EmailChangeVerification($this, $code, $newEmail, $expiresAt));
+ $this->notify(new EmailChangeVerification($this, $code, $newEmail, $expiresAt));
}
public function isEmailChangeCodeValid(string $code): bool
diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php
index c522cd0ca..fbcedf277 100644
--- a/bootstrap/helpers/applications.php
+++ b/bootstrap/helpers/applications.php
@@ -6,6 +6,7 @@
use App\Jobs\VolumeCloneJob;
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
+use App\Models\EnvironmentVariable;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Spatie\Url\Url;
@@ -192,7 +193,7 @@ function clone_application(Application $source, $destination, array $overrides =
$server = $destination->server;
if ($server->team_id !== currentTeam()->id) {
- throw new \RuntimeException('Destination does not belong to the current team.');
+ throw new RuntimeException('Destination does not belong to the current team.');
}
// Prepare name and URL
@@ -211,7 +212,7 @@ function clone_application(Application $source, $destination, array $overrides =
'updated_at',
'additional_servers_count',
'additional_networks_count',
- ])->fill(array_merge([
+ ])->forceFill(array_merge([
'uuid' => $uuid,
'name' => $name,
'fqdn' => $url,
@@ -322,8 +323,8 @@ function clone_application(Application $source, $destination, array $overrides =
destination: $source->destination,
no_questions_asked: true
);
- } catch (\Exception $e) {
- \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
+ } catch (Exception $e) {
+ Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
}
}
}
@@ -344,7 +345,7 @@ function clone_application(Application $source, $destination, array $overrides =
// Clone production environment variables without triggering the created hook
$environmentVariables = $source->environment_variables()->get();
foreach ($environmentVariables as $environmentVariable) {
- \App\Models\EnvironmentVariable::withoutEvents(function () use ($environmentVariable, $newApplication) {
+ EnvironmentVariable::withoutEvents(function () use ($environmentVariable, $newApplication) {
$newEnvironmentVariable = $environmentVariable->replicate([
'id',
'created_at',
@@ -361,7 +362,7 @@ function clone_application(Application $source, $destination, array $overrides =
// Clone preview environment variables
$previewEnvironmentVariables = $source->environment_variables_preview()->get();
foreach ($previewEnvironmentVariables as $previewEnvironmentVariable) {
- \App\Models\EnvironmentVariable::withoutEvents(function () use ($previewEnvironmentVariable, $newApplication) {
+ EnvironmentVariable::withoutEvents(function () use ($previewEnvironmentVariable, $newApplication) {
$newPreviewEnvironmentVariable = $previewEnvironmentVariable->replicate([
'id',
'created_at',
diff --git a/tests/Feature/MassAssignmentProtectionTest.php b/tests/Feature/MassAssignmentProtectionTest.php
new file mode 100644
index 000000000..f6518648f
--- /dev/null
+++ b/tests/Feature/MassAssignmentProtectionTest.php
@@ -0,0 +1,182 @@
+getGuarded();
+ $fillable = $model->getFillable();
+
+ // Model must NOT have $guarded = [] (empty guard = no protection)
+ // It should either have non-empty $guarded OR non-empty $fillable
+ $hasProtection = $guarded !== ['*'] ? count($guarded) > 0 : true;
+ $hasProtection = $hasProtection || count($fillable) > 0;
+
+ expect($hasProtection)
+ ->toBeTrue("Model {$modelClass} has no mass assignment protection (empty \$guarded and empty \$fillable)");
+ }
+ });
+
+ test('Application model blocks mass assignment of relationship IDs', function () {
+ $application = new Application;
+ $dangerousFields = ['id', 'uuid', 'environment_id', 'destination_id', 'destination_type', 'source_id', 'source_type', 'private_key_id', 'repository_project_id'];
+
+ foreach ($dangerousFields as $field) {
+ expect($application->isFillable($field))
+ ->toBeFalse("Application model should not allow mass assignment of '{$field}'");
+ }
+ });
+
+ test('Application model allows mass assignment of user-facing fields', function () {
+ $application = new Application;
+ $userFields = ['name', 'description', 'git_repository', 'git_branch', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'health_check_path', 'limits_memory', 'status'];
+
+ foreach ($userFields as $field) {
+ expect($application->isFillable($field))
+ ->toBeTrue("Application model should allow mass assignment of '{$field}'");
+ }
+ });
+
+ test('Server model has $fillable and no conflicting $guarded', function () {
+ $server = new Server;
+ $fillable = $server->getFillable();
+ $guarded = $server->getGuarded();
+
+ expect($fillable)->not->toBeEmpty('Server model should have explicit $fillable');
+
+ // Guarded should be the default ['*'] when $fillable is set, not []
+ expect($guarded)->not->toBe([], 'Server model should not have $guarded = [] overriding $fillable');
+ });
+
+ test('Server model blocks mass assignment of dangerous fields', function () {
+ $server = new Server;
+
+ // These fields should not be mass-assignable via the API
+ expect($server->isFillable('id'))->toBeFalse();
+ expect($server->isFillable('uuid'))->toBeFalse();
+ expect($server->isFillable('created_at'))->toBeFalse();
+ });
+
+ test('User model blocks mass assignment of auth-sensitive fields', function () {
+ $user = new User;
+
+ expect($user->isFillable('id'))->toBeFalse('User id should not be fillable');
+ expect($user->isFillable('email_verified_at'))->toBeFalse('email_verified_at should not be fillable');
+ expect($user->isFillable('remember_token'))->toBeFalse('remember_token should not be fillable');
+ expect($user->isFillable('two_factor_secret'))->toBeFalse('two_factor_secret should not be fillable');
+ expect($user->isFillable('two_factor_recovery_codes'))->toBeFalse('two_factor_recovery_codes should not be fillable');
+ });
+
+ test('User model allows mass assignment of profile fields', function () {
+ $user = new User;
+
+ expect($user->isFillable('name'))->toBeTrue();
+ expect($user->isFillable('email'))->toBeTrue();
+ expect($user->isFillable('password'))->toBeTrue();
+ });
+
+ test('Team model blocks mass assignment of internal fields', function () {
+ $team = new Team;
+
+ expect($team->isFillable('id'))->toBeFalse();
+ expect($team->isFillable('personal_team'))->toBeFalse('personal_team should not be fillable');
+ });
+
+ test('standalone database models block mass assignment of relationship IDs', function () {
+ $models = [
+ StandalonePostgresql::class,
+ StandaloneRedis::class,
+ StandaloneMysql::class,
+ StandaloneMariadb::class,
+ StandaloneMongodb::class,
+ StandaloneKeydb::class,
+ StandaloneDragonfly::class,
+ StandaloneClickhouse::class,
+ ];
+
+ foreach ($models as $modelClass) {
+ $model = new $modelClass;
+ $dangerousFields = ['id', 'uuid', 'environment_id', 'destination_id', 'destination_type'];
+
+ foreach ($dangerousFields as $field) {
+ expect($model->isFillable($field))
+ ->toBeFalse("Model {$modelClass} should not allow mass assignment of '{$field}'");
+ }
+ }
+ });
+
+ test('standalone database models allow mass assignment of config fields', function () {
+ $model = new StandalonePostgresql;
+ expect($model->isFillable('name'))->toBeTrue();
+ expect($model->isFillable('postgres_user'))->toBeTrue();
+ expect($model->isFillable('postgres_password'))->toBeTrue();
+ expect($model->isFillable('image'))->toBeTrue();
+ expect($model->isFillable('limits_memory'))->toBeTrue();
+
+ $model = new StandaloneRedis;
+ expect($model->isFillable('redis_password'))->toBeTrue();
+
+ $model = new StandaloneMysql;
+ expect($model->isFillable('mysql_root_password'))->toBeTrue();
+
+ $model = new StandaloneMongodb;
+ expect($model->isFillable('mongo_initdb_root_username'))->toBeTrue();
+ });
+
+ test('Application fill ignores non-fillable fields', function () {
+ $application = new Application;
+ $application->fill([
+ 'name' => 'test-app',
+ 'environment_id' => 999,
+ 'destination_id' => 999,
+ 'team_id' => 999,
+ 'private_key_id' => 999,
+ ]);
+
+ expect($application->name)->toBe('test-app');
+ expect($application->environment_id)->toBeNull();
+ expect($application->destination_id)->toBeNull();
+ expect($application->private_key_id)->toBeNull();
+ });
+
+ test('Service model blocks mass assignment of relationship IDs', function () {
+ $service = new Service;
+
+ expect($service->isFillable('id'))->toBeFalse();
+ expect($service->isFillable('uuid'))->toBeFalse();
+ expect($service->isFillable('environment_id'))->toBeFalse();
+ expect($service->isFillable('destination_id'))->toBeFalse();
+ expect($service->isFillable('server_id'))->toBeFalse();
+ });
+});