From f3b14570f27417beef44c338f3378b46d5b5fd06 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Wed, 31 Dec 2025 19:14:10 +0100 Subject: [PATCH] Feedback --- models/migrations/migrations.go | 1 + models/migrations/v1_26/v325.go | 15 ++ models/perm/access/repo_permission.go | 2 +- models/repo/repo_unit.go | 16 +- models/repo/repo_unit_test.go | 12 +- routers/web/org/setting/actions.go | 3 +- routers/web/repo/actions/view.go | 32 +++- routers/web/repo/setting/actions.go | 3 +- services/actions/permission_parser.go | 6 +- services/actions/permission_parser_test.go | 18 +-- tests/integration/actions_job_token_test.go | 159 +++++++++++++++++++- 11 files changed, 233 insertions(+), 34 deletions(-) create mode 100644 models/migrations/v1_26/v325.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index fa11acaee2..e720682505 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -399,6 +399,7 @@ func prepareMigrationTasks() []*migration { newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency), newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness), + newMigration(325, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob), } return preparedMigrations } diff --git a/models/migrations/v1_26/v325.go b/models/migrations/v1_26/v325.go new file mode 100644 index 0000000000..efac821c5d --- /dev/null +++ b/models/migrations/v1_26/v325.go @@ -0,0 +1,15 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "xorm.io/xorm" +) + +func AddTokenPermissionsToActionRunJob(x *xorm.Engine) error { + type ActionRunJob struct { + TokenPermissions string `xorm:"TEXT"` + } + return x.Sync(new(ActionRunJob)) +} diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 434dc15c38..164feccbea 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -357,7 +357,7 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito // Set up per-unit access modes based on configured permissions perm.units = repo.Units perm.unitsMode = make(map[unit.Type]perm_model.AccessMode) - perm.unitsMode[unit.TypeCode] = effectivePerms.Contents + perm.unitsMode[unit.TypeCode] = effectivePerms.Code perm.unitsMode[unit.TypeIssues] = effectivePerms.Issues perm.unitsMode[unit.TypePullRequests] = effectivePerms.PullRequests perm.unitsMode[unit.TypePackages] = effectivePerms.Packages diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index a35ac65963..cbb5cca38d 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -182,8 +182,8 @@ const ( // ActionsTokenPermissions defines the permissions for different repository units type ActionsTokenPermissions struct { - // Contents (repository code) - read/write/none - Contents perm.AccessMode `json:"contents"` + // Code (repository code) - read/write/none + Code perm.AccessMode `json:"contents"` // Issues - read/write/none Issues perm.AccessMode `json:"issues"` // PullRequests - read/write/none @@ -203,7 +203,7 @@ func (p ActionsTokenPermissions) HasAccess(scope string, required perm.AccessMod case "actions": mode = p.Actions case "contents": - mode = p.Contents + mode = p.Code case "issues": mode = p.Issues case "packages": @@ -230,7 +230,7 @@ func (p ActionsTokenPermissions) HasWrite(scope string) bool { func DefaultActionsTokenPermissions(mode ActionsTokenPermissionMode) ActionsTokenPermissions { if mode == ActionsTokenPermissionModeRestricted { return ActionsTokenPermissions{ - Contents: perm.AccessModeRead, + Code: perm.AccessModeRead, Issues: perm.AccessModeRead, PullRequests: perm.AccessModeRead, Packages: perm.AccessModeRead, @@ -240,7 +240,7 @@ func DefaultActionsTokenPermissions(mode ActionsTokenPermissionMode) ActionsToke } // Permissive mode (default) return ActionsTokenPermissions{ - Contents: perm.AccessModeWrite, + Code: perm.AccessModeWrite, Issues: perm.AccessModeWrite, PullRequests: perm.AccessModeWrite, Packages: perm.AccessModeRead, // Packages read by default for security @@ -252,7 +252,7 @@ func DefaultActionsTokenPermissions(mode ActionsTokenPermissionMode) ActionsToke // ForkPullRequestPermissions returns the restricted permissions for fork pull requests func ForkPullRequestPermissions() ActionsTokenPermissions { return ActionsTokenPermissions{ - Contents: perm.AccessModeRead, + Code: perm.AccessModeRead, Issues: perm.AccessModeRead, PullRequests: perm.AccessModeRead, Packages: perm.AccessModeRead, @@ -364,7 +364,7 @@ func (cfg *ActionsConfig) GetMaxTokenPermissions() ActionsTokenPermissions { } // Default max is write for everything except packages return ActionsTokenPermissions{ - Contents: perm.AccessModeWrite, + Code: perm.AccessModeWrite, Issues: perm.AccessModeWrite, PullRequests: perm.AccessModeWrite, Packages: perm.AccessModeWrite, @@ -377,7 +377,7 @@ func (cfg *ActionsConfig) GetMaxTokenPermissions() ActionsTokenPermissions { func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) ActionsTokenPermissions { maxPerms := cfg.GetMaxTokenPermissions() return ActionsTokenPermissions{ - Contents: min(perms.Contents, maxPerms.Contents), + Code: min(perms.Code, maxPerms.Code), Issues: min(perms.Issues, maxPerms.Issues), PullRequests: min(perms.PullRequests, maxPerms.PullRequests), Packages: min(perms.Packages, maxPerms.Packages), diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go index 11b430e485..df91060a89 100644 --- a/models/repo/repo_unit_test.go +++ b/models/repo/repo_unit_test.go @@ -49,7 +49,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { TokenPermissionMode: ActionsTokenPermissionModePermissive, } perms := cfg.GetEffectiveTokenPermissions(false) - assert.Equal(t, perm.AccessModeWrite, perms.Contents) + assert.Equal(t, perm.AccessModeWrite, perms.Code) assert.Equal(t, perm.AccessModeWrite, perms.Issues) assert.Equal(t, perm.AccessModeRead, perms.Packages) // Packages read by default for security }) @@ -59,7 +59,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { TokenPermissionMode: ActionsTokenPermissionModeRestricted, } perms := cfg.GetEffectiveTokenPermissions(false) - assert.Equal(t, perm.AccessModeRead, perms.Contents) + assert.Equal(t, perm.AccessModeRead, perms.Code) assert.Equal(t, perm.AccessModeRead, perms.Issues) assert.Equal(t, perm.AccessModeRead, perms.Packages) }) @@ -70,7 +70,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { } // Even with permissive mode, fork PRs get read-only perms := cfg.GetEffectiveTokenPermissions(true) - assert.Equal(t, perm.AccessModeRead, perms.Contents) + assert.Equal(t, perm.AccessModeRead, perms.Code) assert.Equal(t, perm.AccessModeRead, perms.Issues) assert.Equal(t, perm.AccessModeRead, perms.Packages) }) @@ -78,7 +78,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { t.Run("Clamp Permissions", func(t *testing.T) { cfg := &ActionsConfig{ MaxTokenPermissions: &ActionsTokenPermissions{ - Contents: perm.AccessModeRead, + Code: perm.AccessModeRead, Issues: perm.AccessModeWrite, PullRequests: perm.AccessModeRead, Packages: perm.AccessModeRead, @@ -87,7 +87,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { }, } input := ActionsTokenPermissions{ - Contents: perm.AccessModeWrite, // Should be clamped to Read + Code: perm.AccessModeWrite, // Should be clamped to Read Issues: perm.AccessModeWrite, // Should stay Write PullRequests: perm.AccessModeWrite, // Should be clamped to Read Packages: perm.AccessModeWrite, // Should be clamped to Read @@ -95,7 +95,7 @@ func TestActionsConfigTokenPermissions(t *testing.T) { Wiki: perm.AccessModeRead, // Should stay Read } clamped := cfg.ClampPermissions(input) - assert.Equal(t, perm.AccessModeRead, clamped.Contents) + assert.Equal(t, perm.AccessModeRead, clamped.Code) assert.Equal(t, perm.AccessModeWrite, clamped.Issues) assert.Equal(t, perm.AccessModeRead, clamped.PullRequests) assert.Equal(t, perm.AccessModeRead, clamped.Packages) diff --git a/routers/web/org/setting/actions.go b/routers/web/org/setting/actions.go index 475a70d724..fd5cd723b3 100644 --- a/routers/web/org/setting/actions.go +++ b/routers/web/org/setting/actions.go @@ -76,8 +76,7 @@ func ActionsGeneralPost(ctx *context.Context) { } actionsCfg.MaxTokenPermissions = &repo_model.ActionsTokenPermissions{ - Actions: parseMaxPerm("actions"), - Contents: parseMaxPerm("contents"), + Code: parseMaxPerm("contents"), Issues: parseMaxPerm("issues"), Packages: parseMaxPerm("packages"), PullRequests: parseMaxPerm("pull_requests"), diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index cc70cd4e06..3395ff6ec8 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -34,6 +34,7 @@ import ( context_module "code.gitea.io/gitea/services/context" notify_service "code.gitea.io/gitea/services/notify" + "github.com/nektos/act/pkg/jobparser" "github.com/nektos/act/pkg/model" "gopkg.in/yaml.v3" "xorm.io/builder" @@ -536,8 +537,37 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou } } + // Recalculate permissions on rerun to respect current repo settings + if len(job.WorkflowPayload) > 0 { + singleWorkflow, err := jobparser.Parse(job.WorkflowPayload) + if err != nil { + log.Warn("rerunJob: failed to parse workflow payload for job %d: %v", job.ID, err) + } else { + for _, flow := range singleWorkflow { + wfJobID, wfJob := flow.Job() + if wfJobID == job.JobID { + if job.Run.Repo == nil { + if err := job.Run.LoadRepo(ctx); err != nil { + return err + } + } + cfgUnit := job.Run.Repo.MustGetUnit(ctx, unit.TypeActions) + cfg := cfgUnit.ActionsConfig() + + defaultPerms := cfg.GetEffectiveTokenPermissions(job.Run.IsForkPullRequest) + workflowPerms := actions_service.ParseWorkflowPermissions(flow, defaultPerms) + jobPerms := actions_service.ParseJobPermissions(wfJob, workflowPerms) + finalPerms := cfg.ClampPermissions(jobPerms) + + job.TokenPermissions = repo_model.MarshalTokenPermissions(finalPerms) + break + } + } + } + } + if err := db.WithTx(ctx, func(ctx context.Context) error { - updateCols := []string{"task_id", "status", "started", "stopped", "concurrency_group", "concurrency_cancel", "is_concurrency_evaluated"} + updateCols := []string{"task_id", "status", "started", "stopped", "concurrency_group", "concurrency_cancel", "is_concurrency_evaluated", "token_permissions"} _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, updateCols...) return err }); err != nil { diff --git a/routers/web/repo/setting/actions.go b/routers/web/repo/setting/actions.go index 0ba12e9f30..56ff06e5a2 100644 --- a/routers/web/repo/setting/actions.go +++ b/routers/web/repo/setting/actions.go @@ -177,8 +177,7 @@ func UpdateTokenPermissions(ctx *context.Context) { } actionsCfg.MaxTokenPermissions = &repo_model.ActionsTokenPermissions{ - Actions: parseMaxPerm("actions"), - Contents: parseMaxPerm("contents"), + Code: parseMaxPerm("contents"), Issues: parseMaxPerm("issues"), Packages: parseMaxPerm("packages"), PullRequests: parseMaxPerm("pull_requests"), diff --git a/services/actions/permission_parser.go b/services/actions/permission_parser.go index 4bb80fe669..830fb84c9e 100644 --- a/services/actions/permission_parser.go +++ b/services/actions/permission_parser.go @@ -64,7 +64,7 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok switch node.Value { case "read-all": return repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeRead, + Code: perm.AccessModeRead, Issues: perm.AccessModeRead, PullRequests: perm.AccessModeRead, Packages: perm.AccessModeRead, @@ -73,7 +73,7 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok } case "write-all": return repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeWrite, + Code: perm.AccessModeWrite, Issues: perm.AccessModeWrite, PullRequests: perm.AccessModeWrite, Packages: perm.AccessModeWrite, @@ -106,7 +106,7 @@ func parseRawPermissions(rawPerms *yaml.Node, defaultPerms repo_model.ActionsTok // Map GitHub Actions scopes to Gitea units switch scope { case "contents": - result.Contents = accessMode + result.Code = accessMode case "issues": result.Issues = accessMode case "pull-requests": diff --git a/services/actions/permission_parser_test.go b/services/actions/permission_parser_test.go index f4dbb01705..919815d8f3 100644 --- a/services/actions/permission_parser_test.go +++ b/services/actions/permission_parser_test.go @@ -21,7 +21,7 @@ func TestParseRawPermissions_ReadAll(t *testing.T) { defaultPerms := repo_model.DefaultActionsTokenPermissions(repo_model.ActionsTokenPermissionModePermissive) result := parseRawPermissions(&rawPerms, defaultPerms) - assert.Equal(t, perm.AccessModeRead, result.Contents) + assert.Equal(t, perm.AccessModeRead, result.Code) assert.Equal(t, perm.AccessModeRead, result.Issues) assert.Equal(t, perm.AccessModeRead, result.PullRequests) assert.Equal(t, perm.AccessModeRead, result.Packages) @@ -37,7 +37,7 @@ func TestParseRawPermissions_WriteAll(t *testing.T) { defaultPerms := repo_model.DefaultActionsTokenPermissions(repo_model.ActionsTokenPermissionModeRestricted) result := parseRawPermissions(&rawPerms, defaultPerms) - assert.Equal(t, perm.AccessModeWrite, result.Contents) + assert.Equal(t, perm.AccessModeWrite, result.Code) assert.Equal(t, perm.AccessModeWrite, result.Issues) assert.Equal(t, perm.AccessModeWrite, result.PullRequests) assert.Equal(t, perm.AccessModeWrite, result.Packages) @@ -59,7 +59,7 @@ wiki: write assert.NoError(t, err) defaultPerms := repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeNone, + Code: perm.AccessModeNone, Issues: perm.AccessModeNone, PullRequests: perm.AccessModeNone, Packages: perm.AccessModeNone, @@ -68,7 +68,7 @@ wiki: write } result := parseRawPermissions(&rawPerms, defaultPerms) - assert.Equal(t, perm.AccessModeWrite, result.Contents) + assert.Equal(t, perm.AccessModeWrite, result.Code) assert.Equal(t, perm.AccessModeRead, result.Issues) assert.Equal(t, perm.AccessModeNone, result.PullRequests) assert.Equal(t, perm.AccessModeWrite, result.Packages) @@ -90,7 +90,7 @@ issues: write result := parseRawPermissions(&rawPerms, defaultPerms) // Overridden scopes - assert.Equal(t, perm.AccessModeRead, result.Contents) + assert.Equal(t, perm.AccessModeRead, result.Code) assert.Equal(t, perm.AccessModeWrite, result.Issues) // Non-overridden scopes keep defaults assert.Equal(t, perm.AccessModeWrite, result.PullRequests) @@ -107,7 +107,7 @@ func TestParseRawPermissions_EmptyNode(t *testing.T) { result := parseRawPermissions(&rawPerms, defaultPerms) // Should return defaults - assert.Equal(t, defaultPerms.Contents, result.Contents) + assert.Equal(t, defaultPerms.Code, result.Code) assert.Equal(t, defaultPerms.Issues, result.Issues) } @@ -116,7 +116,7 @@ func TestParseRawPermissions_NilNode(t *testing.T) { result := parseRawPermissions(nil, defaultPerms) // Should return defaults - assert.Equal(t, defaultPerms.Contents, result.Contents) + assert.Equal(t, defaultPerms.Code, result.Code) assert.Equal(t, defaultPerms.Issues, result.Issues) } @@ -142,7 +142,7 @@ func TestParseAccessMode(t *testing.T) { func TestMarshalUnmarshalTokenPermissions(t *testing.T) { original := repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeWrite, + Code: perm.AccessModeWrite, Issues: perm.AccessModeRead, PullRequests: perm.AccessModeNone, Packages: perm.AccessModeWrite, @@ -164,6 +164,6 @@ func TestUnmarshalTokenPermissions_EmptyString(t *testing.T) { result, err := repo_model.UnmarshalTokenPermissions("") assert.NoError(t, err) // Should return zero-value struct - assert.Equal(t, perm.AccessModeNone, result.Contents) + assert.Equal(t, perm.AccessModeNone, result.Code) assert.Equal(t, perm.AccessModeNone, result.Issues) } diff --git a/tests/integration/actions_job_token_test.go b/tests/integration/actions_job_token_test.go index 26686cee6f..d69755d811 100644 --- a/tests/integration/actions_job_token_test.go +++ b/tests/integration/actions_job_token_test.go @@ -269,7 +269,7 @@ func TestActionsTokenPermissionsClamping(t *testing.T) { Config: &repo_model.ActionsConfig{ TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, MaxTokenPermissions: &repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeRead, // Max is Read - will clamp default Write to Read + Code: perm.AccessModeRead, // Max is Read - will clamp default Write to Read }, }, }) @@ -535,7 +535,7 @@ func TestActionsWorkflowPermissionsKeyword(t *testing.T) { // Create an Actions run job with TokenPermissions set (simulating a workflow with permissions: read-all) // This is what the permission parser does when parsing the workflow YAML readOnlyPerms := repo_model.ActionsTokenPermissions{ - Contents: perm.AccessModeRead, + Code: perm.AccessModeRead, Issues: perm.AccessModeRead, PullRequests: perm.AccessModeRead, Packages: perm.AccessModeRead, @@ -607,6 +607,161 @@ func TestActionsWorkflowPermissionsKeyword(t *testing.T) { }, ContentBase64: base64.StdEncoding.EncodeToString([]byte("Should Not Be Created")), })) + + // Subtest: Verify that job-level overriding works + // Create another job with `permissions: contents: write` to override `read-all` + // Logic: Workflow read-all -> Code: Read. Job contents:write -> Code: Write. + // Repo is Permissive (Max: Write). So Result: Write. + overridePerms := repo_model.ActionsTokenPermissions{ + Code: perm.AccessModeWrite, + Issues: perm.AccessModeRead, + PullRequests: perm.AccessModeRead, + Packages: perm.AccessModeRead, + Actions: perm.AccessModeRead, + Wiki: perm.AccessModeRead, + } + overridePermsJSON := repo_model.MarshalTokenPermissions(overridePerms) + + jobOverride := &actions_model.ActionRunJob{ + RunID: run.ID, + RepoID: repository.ID, + OwnerID: repository.Owner.ID, + CommitSHA: "abc123", + Name: "test-job-override", + JobID: "test-job-override", + Status: actions_model.StatusRunning, + TokenPermissions: overridePermsJSON, + } + require.NoError(t, db.Insert(t.Context(), jobOverride)) + + taskOverride := &actions_model.ActionTask{ + JobID: jobOverride.ID, + RepoID: repository.ID, + Status: actions_model.StatusRunning, + IsForkPullRequest: false, + } + require.NoError(t, taskOverride.GenerateToken()) + require.NoError(t, db.Insert(t.Context(), taskOverride)) + jobOverride.TaskID = taskOverride.ID + _, err = db.GetEngine(t.Context()).ID(jobOverride.ID).Cols("task_id").Update(jobOverride) + require.NoError(t, err) + + testCtxOverride := APITestContext{ + Session: session, + Token: taskOverride.Token, + Username: "user2", + Reponame: "repo-workflow-perms-kw", + } + testCtxOverride.ExpectedCode = http.StatusCreated + t.Run("GITEA_TOKEN Create File (Write ALLOWED by job override)", doAPICreateFile(testCtxOverride, "should-succeed-override.txt", &structs.CreateFileOptions{ + FileOptions: structs.FileOptions{ + BranchName: "master", + Message: "this should succeed due to job permissions override", + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("Should Be Created")), + })) + })) + }) +} + +func TestActionsRerunPermissions(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user2") + httpContext := NewAPITestContext(t, "user2", "repo-rerun-perms", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + + t.Run("Rerun Permissions", doAPICreateRepository(httpContext, false, func(t *testing.T, repository structs.Repository) { + // 1. Enable Actions with PERMISSIVE mode + err := db.Insert(t.Context(), &repo_model.RepoUnit{ + RepoID: repository.ID, + Type: unit_model.TypeActions, + Config: &repo_model.ActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive, + }, + }) + require.NoError(t, err) + + // 2. Create Run and Job with implicit permissions (no parsed perms stored yet) + // or with parsed perms that allow write (Permissive default) + workflowPayload := ` +name: Test Rerun +on: workflow_dispatch +jobs: + test-rerun: + runs-on: ubuntu-latest + steps: + - run: echo hello +` + run := &actions_model.ActionRun{ + RepoID: repository.ID, + OwnerID: repository.Owner.ID, + Title: "Test Rerun", + Status: actions_model.StatusSuccess, // Run finished + Ref: "refs/heads/master", + CommitSHA: "abc123", + WorkflowID: "test-rerun.yaml", + TriggerUserID: repository.Owner.ID, + } + require.NoError(t, db.Insert(t.Context(), run)) + + // Initial permissions: Permissive (Write) + initialPerms := repo_model.ActionsTokenPermissions{ + Code: perm.AccessModeWrite, + } + + job := &actions_model.ActionRunJob{ + RunID: run.ID, + RepoID: repository.ID, + OwnerID: repository.Owner.ID, + CommitSHA: "abc123", + Name: "test-rerun", + JobID: "test-rerun", + Status: actions_model.StatusSuccess, // Job finished + WorkflowPayload: []byte(workflowPayload), + TokenPermissions: repo_model.MarshalTokenPermissions(initialPerms), + } + require.NoError(t, db.Insert(t.Context(), job)) + + // 3. Change Repo Settings to RESTRICTED + // We need to update the RepoUnit config + unitConfig := &repo_model.ActionsConfig{ + TokenPermissionMode: repo_model.ActionsTokenPermissionModeRestricted, + } + // Update the specific unit + // Need to find the unit first + repo, err := repo_model.GetRepositoryByID(t.Context(), repository.ID) + require.NoError(t, err) + unit, err := repo.GetUnit(t.Context(), unit_model.TypeActions) + require.NoError(t, err) + + unit.Config = unitConfig + require.NoError(t, repo_model.UpdateRepoUnit(t.Context(), unit)) + + // 4. Trigger Rerun via Web Handler + // POST /:username/:reponame/actions/runs/:index/rerun + // We need to know operation run index. Since it's the first run, it should be 1? + // ActionRun.Index is auto-increment but not set in my insert. + // Ideally we use CreateRun which handles index. + // Let's manually set index 1. + run.Index = 1 + _, err = db.GetEngine(t.Context()).ID(run.ID).Cols("index").Update(run) + require.NoError(t, err) + + req := NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/rerun", "user2", "repo-rerun-perms", run.Index)) + session.MakeRequest(t, req, http.StatusOK) + + // 5. Verify TokenPermissions in DB are now Restricted (Read-only) + // Reload job + jobReload := new(actions_model.ActionRunJob) + has, err := db.GetEngine(t.Context()).ID(job.ID).Get(jobReload) + require.NoError(t, err) + assert.True(t, has) + + // Check permissions + perms, err := repo_model.UnmarshalTokenPermissions(jobReload.TokenPermissions) + require.NoError(t, err) + + // Should be restricted (Read) + assert.Equal(t, perm.AccessModeRead, perms.Code, "Permissions should be restricted to Read after rerun in restricted mode") })) }) }