fix(security): enforce team-scoped project/env lookups in onboarding
Use firstOrFail() for team-scoped project and environment lookups across new-project Livewire flows so missing or cross-team UUIDs fail closed. Also dispatch an error when boarding selects a non-owned project, and update IDOR feature tests for the new error/exception behavior.
This commit is contained in:
parent
e36622fdfb
commit
3ba4553df5
8 changed files with 17 additions and 14 deletions
|
|
@ -432,6 +432,9 @@ public function getProjects()
|
||||||
public function selectExistingProject()
|
public function selectExistingProject()
|
||||||
{
|
{
|
||||||
$this->createdProject = Project::ownedByCurrentTeam()->find($this->selectedProject);
|
$this->createdProject = Project::ownedByCurrentTeam()->find($this->selectedProject);
|
||||||
|
if (! $this->createdProject) {
|
||||||
|
return $this->dispatch('error', 'Project not found.');
|
||||||
|
}
|
||||||
$this->currentState = 'create-resource';
|
$this->currentState = 'create-resource';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ public function submit()
|
||||||
// Validate for command injection BEFORE saving to database
|
// Validate for command injection BEFORE saving to database
|
||||||
validateDockerComposeForInjection($this->dockerComposeRaw);
|
validateDockerComposeForInjection($this->dockerComposeRaw);
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||||
|
|
||||||
$destination_uuid = $this->query['destination'];
|
$destination_uuid = $this->query['destination'];
|
||||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,8 @@ public function submit()
|
||||||
}
|
}
|
||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||||
|
|
||||||
// Append @sha256 to image name if using digest and not already present
|
// Append @sha256 to image name if using digest and not already present
|
||||||
$imageName = $parser->getFullImageNameWithoutTag();
|
$imageName = $parser->getFullImageNameWithoutTag();
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,8 @@ public function submit()
|
||||||
}
|
}
|
||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||||
|
|
||||||
$application = Application::create([
|
$application = Application::create([
|
||||||
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
|
||||||
|
|
|
||||||
|
|
@ -144,8 +144,8 @@ public function submit()
|
||||||
// Note: git_repository has already been validated and transformed in get_git_source()
|
// Note: git_repository has already been validated and transformed in get_git_source()
|
||||||
// It may now be in SSH format (git@host:repo.git) which is valid for deploy keys
|
// It may now be in SSH format (git@host:repo.git) which is valid for deploy keys
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||||
if ($this->git_source === 'other') {
|
if ($this->git_source === 'other') {
|
||||||
$application_init = [
|
$application_init = [
|
||||||
'name' => generate_random_name(),
|
'name' => generate_random_name(),
|
||||||
|
|
|
||||||
|
|
@ -278,8 +278,8 @@ public function submit()
|
||||||
}
|
}
|
||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $environment_uuid)->first();
|
$environment = $project->environments()->where('uuid', $environment_uuid)->firstOrFail();
|
||||||
|
|
||||||
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) {
|
if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) {
|
||||||
$server = $destination->server;
|
$server = $destination->server;
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ public function submit()
|
||||||
}
|
}
|
||||||
$destination_class = $destination->getMorphClass();
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
|
||||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
|
||||||
|
|
||||||
$port = get_port_from_dockerfile($this->dockerfile);
|
$port = get_port_from_dockerfile($this->dockerfile);
|
||||||
if (! $port) {
|
if (! $port) {
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@
|
||||||
->call('selectExistingProject');
|
->call('selectExistingProject');
|
||||||
|
|
||||||
expect($component->get('createdProject'))->toBeNull();
|
expect($component->get('createdProject'))->toBeNull();
|
||||||
|
$component->assertDispatched('error');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('boarding selectExistingProject can load own team project', function () {
|
test('boarding selectExistingProject can load own team project', function () {
|
||||||
|
|
@ -115,8 +116,7 @@
|
||||||
describe('DeleteProject IDOR (GHSA-qfcc-2fm3-9q42)', function () {
|
describe('DeleteProject IDOR (GHSA-qfcc-2fm3-9q42)', function () {
|
||||||
test('cannot mount DeleteProject with project from another team', function () {
|
test('cannot mount DeleteProject with project from another team', function () {
|
||||||
// Should throw ModelNotFoundException (404) because team-scoped query won't find it
|
// Should throw ModelNotFoundException (404) because team-scoped query won't find it
|
||||||
Livewire::test(DeleteProject::class, ['project_id' => $this->projectB->id])
|
Livewire::test(DeleteProject::class, ['project_id' => $this->projectB->id]);
|
||||||
->assertStatus(500); // findOrFail throws ModelNotFoundException
|
|
||||||
})->throws(\Illuminate\Database\Eloquent\ModelNotFoundException::class);
|
})->throws(\Illuminate\Database\Eloquent\ModelNotFoundException::class);
|
||||||
|
|
||||||
test('can mount DeleteProject with own team project', function () {
|
test('can mount DeleteProject with own team project', function () {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue