fix(api): require write permission to choose a preview commit

- Treat a commit value on the preview endpoint as a preview change
- Update the API docs, the MCP tool description, and the change file
- Add a test case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-11-09 10:50:51 +01:00
co-authored by Claude Opus 5.5
parent 2466ccb8fd
commit 769bddb326
6 changed files with 12 additions and 10 deletions
@@ -4,6 +4,6 @@ commit: c75e7078b7
---
Restricted preview changes, rollbacks and repository URL changes
- Opening a preview or changing its pull request URL, Git type or Docker tag through the API needs write access, a deploy token can only redeploy an existing preview
- Opening a preview, changing its pull request URL, Git type or Docker tag, or choosing a commit through the API needs write access, a deploy token can only redeploy an existing preview
- A rollback goes only to a listed rollback image or a successful production deployment
- A new `git_repository` of an application is validated like in the UI
@@ -106,7 +106,7 @@ class ApplicationPreviewsController extends Controller
#[OA\Post(
summary: 'Create Preview Deployment',
description: 'Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview or changing its pull request URL, git type, or docker tag also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.',
description: 'Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview, changing its pull request URL, git type, or docker tag, or choosing a commit also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.',
path: '/applications/{uuid}/previews',
operationId: 'create-preview-deployment-by-application-uuid',
security: [['bearerAuth' => []]],
@@ -120,7 +120,7 @@ class ApplicationPreviewsController extends Controller
new OA\Property(property: 'pull_request_id', type: 'integer', minimum: 1, maximum: 2147483647, example: 42),
new OA\Property(property: 'pull_request_html_url', type: 'string', nullable: true, example: 'https://github.com/org/repo/pull/42'),
new OA\Property(property: 'git_type', type: 'string', nullable: true, enum: self::GIT_TYPES, description: 'Git provider that hosts the pull request. Sets the ref Coolify fetches (pull/{id}/head or merge-requests/{id}/head).'),
new OA\Property(property: 'commit', type: 'string', nullable: true, description: 'Commit SHA to deploy. Required for Bitbucket.'),
new OA\Property(property: 'commit', type: 'string', nullable: true, description: 'Commit SHA to deploy. Required for a new Bitbucket preview. Needs the write permission.'),
new OA\Property(property: 'docker_tag', type: 'string', nullable: true, description: 'Docker image tag. Docker Image applications only.'),
new OA\Property(property: 'force', type: 'boolean', default: false, description: 'Rebuild without cache.'),
new OA\Property(property: 'instant_deploy', type: 'boolean', default: true, description: 'Queue a deployment. Set to false to only create the preview, for example to set its domains first.'),
@@ -230,11 +230,12 @@ class ApplicationPreviewsController extends Controller
$changesPreview = ! $preview
|| ($pullRequestHtmlUrl !== null && $pullRequestHtmlUrl !== $preview->pull_request_html_url)
|| ($request->input('git_type') !== null && $request->input('git_type') !== $preview->git_type)
|| ($dockerTag !== null && $dockerTag !== $preview->docker_registry_image_tag);
|| ($dockerTag !== null && $dockerTag !== $preview->docker_registry_image_tag)
|| $request->filled('commit');
if ($changesPreview) {
if (! $request->user()->tokenCan('write') && ! $request->user()->tokenCan('root')) {
return response()->json([
'message' => 'Missing required permissions: write. Opening a preview or changing its pull request URL, git type, or docker tag needs the write permission. The deploy permission only redeploys an existing preview.',
'message' => 'Missing required permissions: write. Opening a preview, changing its pull request URL, git type, or docker tag, or choosing a commit needs the write permission. The deploy permission only redeploys an existing preview.',
], 403);
}
$this->authorize('update', $application);
+1 -1
View File
@@ -11,7 +11,7 @@ class ManagePreview extends RestActionTool
{
protected string $name = 'manage_preview';
protected string $description = 'Create (and deploy), update the domains of, or delete a pull request preview deployment of an application of the authenticated team. create requires deploy ability to redeploy an existing preview, and also write ability to open a preview or change its pull request URL, git type, or docker tag; update requires write, delete requires delete. Same validation and permissions as the REST API.';
protected string $description = 'Create (and deploy), update the domains of, or delete a pull request preview deployment of an application of the authenticated team. create requires deploy ability to redeploy an existing preview, and also write ability to open a preview, change its pull request URL, git type, or docker tag, or choose a commit; update requires write, delete requires delete. Same validation and permissions as the REST API.';
protected function actions(): array
{
+2 -2
View File
@@ -62,7 +62,7 @@
"Applications"
],
"summary": "Create Preview Deployment",
"description": "Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview or changing its pull request URL, git type, or docker tag also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.",
"description": "Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview, changing its pull request URL, git type, or docker tag, or choosing a commit also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.",
"operationId": "create-preview-deployment-by-application-uuid",
"parameters": [
{
@@ -111,7 +111,7 @@
]
},
"commit": {
"description": "Commit SHA to deploy. Required for Bitbucket.",
"description": "Commit SHA to deploy. Required for a new Bitbucket preview. Needs the write permission.",
"type": [
"string",
"null"
+2 -2
View File
@@ -42,7 +42,7 @@ paths:
tags:
- Applications
summary: 'Create Preview Deployment'
description: 'Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview or changing its pull request URL, git type, or docker tag also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.'
description: 'Open a preview deployment for a pull request and queue its deployment. When the preview already exists, it is redeployed. A token with the deploy permission can only redeploy an existing preview without changes; opening a preview, changing its pull request URL, git type, or docker tag, or choosing a commit also needs the write permission. Git based applications need git_type unless they use a GitHub or GitLab App source; Bitbucket also needs commit. Docker Image applications need docker_tag for a new preview.'
operationId: create-preview-deployment-by-application-uuid
parameters:
-
@@ -73,7 +73,7 @@ paths:
type: [string, 'null']
enum: [github, gitlab, gitea, bitbucket]
commit:
description: 'Commit SHA to deploy. Required for Bitbucket.'
description: 'Commit SHA to deploy. Required for a new Bitbucket preview. Needs the write permission.'
type: [string, 'null']
docker_tag:
description: 'Docker image tag. Docker Image applications only.'
@@ -718,6 +718,7 @@ describe('POST /api/v1/applications/{uuid}/previews', function () {
})->with([
'git type' => [['git_type' => 'gitlab']],
'pull request url' => [['pull_request_html_url' => 'https://github.com/other/repo/pull/42']],
'commit' => [['commit' => 'abcdef1234567']],
]);
test('a deploy-only token can redeploy an existing preview without changes', function () {