mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-12 02:27:08 +01:00
Feedback
This commit is contained in:
parent
2e7bd47be6
commit
f3b14570f2
@ -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
|
||||
}
|
||||
|
||||
15
models/migrations/v1_26/v325.go
Normal file
15
models/migrations/v1_26/v325.go
Normal file
@ -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))
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user