fix(api): check deploy access before a deploy writes a Docker image preview

The deploy endpoint now checks the deploy permission before it creates a
preview or changes its image tag, not only before it queues the deployment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-11-04 11:32:42 +01:00
co-authored by Claude Opus 5.5
parent ca27224628
commit 25b4236e35
2 changed files with 37 additions and 0 deletions
@@ -429,6 +429,11 @@ class DeployController extends Controller
$preview = null;
if ($pr !== 0) {
if ($resource instanceof Application && $resource->build_pack === 'dockerimage') {
if (! auth()->user()?->can('deploy', $resource)) {
$deployments->push(['message' => 'Unauthorized to deploy this application.', 'resource_uuid' => $uuid]);
continue;
}
$preview = $this->upsertDockerImagePreview($resource, $pr, $dockerTag);
$dockerTagForResource = $preview?->docker_registry_image_tag;
} else {
@@ -152,3 +152,35 @@ test('it rejects docker_tag for non docker image applications', function () {
$response->assertSuccessful();
$response->assertJsonPath('deployments.0.message', 'docker_tag can only be used with Docker Image applications.');
});
test('a member without deploy access cannot create or change a docker image preview', function () {
$application = createDockerImageApplication($this->environment, $this->destination);
ApplicationPreview::create([
'application_id' => $application->id,
'pull_request_id' => 7,
'pull_request_html_url' => '',
'docker_registry_image_tag' => 'pr_7',
]);
$otherProject = Project::factory()->create(['team_id' => $this->team->id]);
$member = User::factory()->create();
$this->team->members()->attach($member->id, [
'role' => 'member',
'access' => json_encode([
['scope' => 'team', 'levels' => ['read']],
['scope' => 'project', 'uuid' => $otherProject->uuid, 'levels' => ['deploy']],
]),
]);
$token = $member->createToken('member-deploy', ['read', 'deploy'], null, $this->team->id)->plainTextToken;
foreach ([[1234, 'pr_1234'], [7, 'attacker']] as [$pullRequestId, $dockerTag]) {
$this->withHeaders(['Authorization' => 'Bearer '.$token])->postJson('/api/v1/deploy', [
'uuid' => $application->uuid,
'pull_request_id' => $pullRequestId,
'docker_tag' => $dockerTag,
])->assertForbidden();
}
expect($application->previews()->pluck('docker_registry_image_tag', 'pull_request_id')->all())->toBe([7 => 'pr_7']);
expect($application->deployment_queue()->count())->toBe(0);
});