From 769bddb3261336d88a02ead379c5614cbdfa5b98 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:50:51 +0100 Subject: [PATCH] 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 --- ...estricted-preview-changes-rollbacks-and-repository.md | 2 +- .../Controllers/Api/ApplicationPreviewsController.php | 9 +++++---- app/Mcp/Tools/ManagePreview.php | 2 +- openapi.json | 4 ++-- openapi.yaml | 4 ++-- tests/Feature/ApplicationPreviewApiTest.php | 1 + 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.changes/restricted-preview-changes-rollbacks-and-repository.md b/.changes/restricted-preview-changes-rollbacks-and-repository.md index e50cfda6a7..0f29546f3b 100644 --- a/.changes/restricted-preview-changes-rollbacks-and-repository.md +++ b/.changes/restricted-preview-changes-rollbacks-and-repository.md @@ -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 diff --git a/app/Http/Controllers/Api/ApplicationPreviewsController.php b/app/Http/Controllers/Api/ApplicationPreviewsController.php index 9e37a0e9a1..fe1e91a94b 100644 --- a/app/Http/Controllers/Api/ApplicationPreviewsController.php +++ b/app/Http/Controllers/Api/ApplicationPreviewsController.php @@ -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); diff --git a/app/Mcp/Tools/ManagePreview.php b/app/Mcp/Tools/ManagePreview.php index 1b7f35f976..cb484d83d2 100644 --- a/app/Mcp/Tools/ManagePreview.php +++ b/app/Mcp/Tools/ManagePreview.php @@ -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 { diff --git a/openapi.json b/openapi.json index eceaa73d0f..0764e0e8ca 100644 --- a/openapi.json +++ b/openapi.json @@ -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" diff --git a/openapi.yaml b/openapi.yaml index 6113596743..f1c7efdcac 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -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.' diff --git a/tests/Feature/ApplicationPreviewApiTest.php b/tests/Feature/ApplicationPreviewApiTest.php index 8ef7c4769d..b133a4e805 100644 --- a/tests/Feature/ApplicationPreviewApiTest.php +++ b/tests/Feature/ApplicationPreviewApiTest.php @@ -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 () {