0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-23 21:31:40 +02:00

feedback fixes

This commit is contained in:
Excellencedev 2026-01-16 15:38:11 +01:00
parent f7fc8792d5
commit feb791cccf
6 changed files with 125 additions and 62 deletions

View File

@ -22,6 +22,7 @@ func GetOrgActionsConfig(ctx context.Context, orgID int64) (*repo_model.ActionsC
cfg := &repo_model.ActionsConfig{} cfg := &repo_model.ActionsConfig{}
if val == "" { if val == "" {
// Return defaults if no config exists // Return defaults if no config exists
cfg.AllowCrossRepoAccess = true
return cfg, nil return cfg, nil
} }

View File

@ -311,15 +311,16 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
// The task repo can access the current repo only if the task repo is private and // The task repo can access the current repo only if the task repo is private and
// the owner of the task repo is a collaborative owner of the current repo. // the owner of the task repo is a collaborative owner of the current repo.
// FIXME should owner's visibility also be considered here? // FIXME should owner's visibility also be considered here?
//
// If not, we check if they are in the same org and cross-repo access is allowed.
// If allowed, we grant Read Access (consistent with old behavior and package access).
// If NOT allowed (checked above for sameOrg), we fall through to here.
// check permission like simple user but limit to read-only if !isSameOrg {
perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser()) return perm, nil
if err != nil {
return perm, err
} }
perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead)
return perm, nil
} }
// Cross-repo access is always read-only // Cross-repo access is always read-only
perm.SetUnitsWithDefaultAccessMode(repo.Units, perm_model.AccessModeRead) perm.SetUnitsWithDefaultAccessMode(repo.Units, perm_model.AccessModeRead)
return perm, nil return perm, nil

View File

@ -303,9 +303,6 @@ type ActionsConfig struct {
CollaborativeOwnerIDs []int64 CollaborativeOwnerIDs []int64
// TokenPermissionMode defines the default permission mode (permissive, restricted, or custom) // TokenPermissionMode defines the default permission mode (permissive, restricted, or custom)
TokenPermissionMode ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"` TokenPermissionMode ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"`
// DefaultTokenPermissions defines the specific permissions for workflow tokens when TokenPermissionMode is set to "custom"
// and no "permissions" keyword is defined in the workflow YAML.
DefaultTokenPermissions *ActionsTokenPermissions `json:"default_token_permissions,omitempty"`
// MaxTokenPermissions defines the absolute maximum permissions any token can have in this context. // MaxTokenPermissions defines the absolute maximum permissions any token can have in this context.
// Workflow YAML "permissions" keywords can reduce permissions but never exceed this ceiling. // Workflow YAML "permissions" keywords can reduce permissions but never exceed this ceiling.
MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"` MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
@ -366,9 +363,8 @@ func (cfg *ActionsConfig) GetEffectiveTokenPermissions(isForkPullRequest bool) A
return ForkPullRequestPermissions() return ForkPullRequestPermissions()
} }
// Use custom default permissions if set if cfg.GetTokenPermissionMode() == ActionsTokenPermissionModeCustom {
if cfg.DefaultTokenPermissions != nil { return cfg.GetMaxTokenPermissions()
return *cfg.DefaultTokenPermissions
} }
// Otherwise use mode-based defaults // Otherwise use mode-based defaults

View File

@ -3731,6 +3731,7 @@
"git.filemode.executable_file": "Executable", "git.filemode.executable_file": "Executable",
"git.filemode.symbolic_link": "Symlink", "git.filemode.symbolic_link": "Symlink",
"git.filemode.submodule": "Submodule", "git.filemode.submodule": "Submodule",
"org.repos.none": "No repositories.",
"actions.general.token_permissions.title": "Action Token Permissions", "actions.general.token_permissions.title": "Action Token Permissions",
"actions.general.token_permissions.desc": "Configure the default permissions for the GITEA_TOKEN running in this repository.", "actions.general.token_permissions.desc": "Configure the default permissions for the GITEA_TOKEN running in this repository.",
"actions.general.token_permissions.mode": "Default Token Permissions", "actions.general.token_permissions.mode": "Default Token Permissions",
@ -3744,7 +3745,7 @@
"actions.general.token_permissions.access_read": "Read", "actions.general.token_permissions.access_read": "Read",
"actions.general.token_permissions.access_write": "Write", "actions.general.token_permissions.access_write": "Write",
"actions.general.token_permissions.code": "Code", "actions.general.token_permissions.code": "Code",
"actions.general.token_permissions.code.description": "Repository contents, commits, branches, downloads, releases, and merges.", "actions.general.token_permissions.code.description": "Repository contents, commits, branches, downloads, and merges.",
"actions.general.token_permissions.issues": "Issues", "actions.general.token_permissions.issues": "Issues",
"actions.general.token_permissions.issues.description": "Issues and related comments, assignees, labels, and milestones.", "actions.general.token_permissions.issues.description": "Issues and related comments, assignees, labels, and milestones.",
"actions.general.token_permissions.pull_requests": "Pull Requests", "actions.general.token_permissions.pull_requests": "Pull Requests",

View File

@ -52,10 +52,7 @@
</a> </a>
</div> </div>
<div class="flex-item-trailing"> <div class="flex-item-trailing">
<form method="post" action="{{$.Link}}/allowed_repos/remove?repo_id={{.ID}}"> <button class="ui red small button" type="submit" formaction="{{$.Link}}/allowed_repos/remove?repo_id={{.ID}}">{{ctx.Locale.Tr "remove"}}</button>
{{$.CsrfTokenHtml}}
<button type="submit" class="ui red small button">{{ctx.Locale.Tr "remove"}}</button>
</form>
</div> </div>
</div> </div>
{{else}} {{else}}
@ -69,15 +66,14 @@
<h5 class="ui header"> <h5 class="ui header">
{{ctx.Locale.Tr "actions.general.token_permissions.add_repo"}} {{ctx.Locale.Tr "actions.general.token_permissions.add_repo"}}
</h5> </h5>
<form class="ui form tw-flex" action="{{$.Link}}/allowed_repos/add" method="post"> <div class="ui form tw-flex">
{{.CsrfTokenHtml}}
<div data-global-init="initSearchRepoBox" data-uid="{{.Org.AsUser.ID}}" class="ui search tw-flex-1"> <div data-global-init="initSearchRepoBox" data-uid="{{.Org.AsUser.ID}}" class="ui search tw-flex-1">
<div class="ui input"> <div class="ui input">
<input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.search_repo"}}" autocomplete="off" required> <input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.search_repo"}}" autocomplete="off" required>
</div> </div>
</div> </div>
<button class="ui green button tw-ml-2">{{ctx.Locale.Tr "add"}}</button> <button class="ui green button tw-ml-2" type="submit" formaction="{{$.Link}}/allowed_repos/add">{{ctx.Locale.Tr "add"}}</button>
</form> </div>
</div> </div>
<div class="divider"></div> <div class="divider"></div>

View File

@ -177,7 +177,6 @@ func testActionsTokenPermissionsMode(u *url.URL, mode string, expectReadOnly boo
require.NoError(t, err, "Actions unit should exist for repo4") require.NoError(t, err, "Actions unit should exist for repo4")
actionsCfg := actionsUnit.ActionsConfig() actionsCfg := actionsUnit.ActionsConfig()
actionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionMode(mode) actionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionMode(mode)
actionsCfg.DefaultTokenPermissions = nil // Ensure no custom permissions override the mode
actionsCfg.MaxTokenPermissions = nil // Ensure no max permissions interfere actionsCfg.MaxTokenPermissions = nil // Ensure no max permissions interfere
// Update the config // Update the config
actionsUnit.Config = actionsCfg actionsUnit.Config = actionsCfg
@ -265,51 +264,120 @@ func testActionsTokenPermissionsMode(u *url.URL, mode string, expectReadOnly boo
func TestActionsTokenPermissionsClamping(t *testing.T) { func TestActionsTokenPermissionsClamping(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
httpContext := NewAPITestContext(t, "user2", "repo-clamping", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
t.Run("Create Repository", doAPICreateRepository(httpContext, false, func(t *testing.T, repository structs.Repository) { session := loginUser(t, user2.Name)
// Enable Actions unit with Clamping Config token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
err := db.Insert(t.Context(), &repo_model.RepoUnit{
RepoID: repository.ID,
Type: unit_model.TypeActions,
Config: &repo_model.ActionsConfig{
TokenPermissionMode: repo_model.ActionsTokenPermissionModePermissive,
MaxTokenPermissions: &repo_model.ActionsTokenPermissions{
Code: perm.AccessModeRead, // Max is Read - will clamp default Write to Read
},
},
})
require.NoError(t, err)
// Create Task and Token // Create Repo
task := &actions_model.ActionTask{ apiRepo := createActionsTestRepo(t, token, "repo-clamping", false)
RepoID: repository.ID, repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
Status: actions_model.StatusRunning, httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
IsForkPullRequest: false, defer doAPIDeleteRepository(httpContext)(t)
}
require.NoError(t, task.GenerateToken())
require.NoError(t, db.Insert(t.Context(), task))
// Verify Token Permissions // Mock Runner
session := emptyTestSession(t) runner := newMockRunner()
testCtx := APITestContext{ runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
Session: session,
Token: task.Token,
Username: "user2",
Reponame: "repo-clamping",
}
// 1. Try to Write (Create File) - Should Fail (403) because Max is Read // Set Clamping Config: Custom Mode (Default=Max), Max Code = Read
testCtx.ExpectedCode = http.StatusForbidden req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", repo.OwnerName, repo.Name), map[string]string{
t.Run("Fail to Create File (Max Clamping)", doAPICreateFile(testCtx, "clamping.txt", &structs.CreateFileOptions{ "token_permission_mode": "custom",
ContentBase64: base64.StdEncoding.EncodeToString([]byte("test")), "max_code": "read",
})) })
session.MakeRequest(t, req, http.StatusSeeOther)
// 2. Try to Read (Get Repository) - Should Succeed (200) // Create workflow requesting Write
testCtx.ExpectedCode = http.StatusOK wfTreePath := ".gitea/workflows/clamping.yml"
t.Run("Get Repository (Read Allowed)", doAPIGetRepository(testCtx, func(t *testing.T, r structs.Repository) { wfFileContent := `name: Clamping
assert.Equal(t, "repo-clamping", r.Name) on: [push]
})) permissions:
contents: write
jobs:
job-clamping:
runs-on: ubuntu-latest
steps:
- run: echo test
`
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts)
// Fetch task
runnerTask := runner.fetchTask(t)
taskToken := runnerTask.Secrets["GITEA_TOKEN"]
require.NotEmpty(t, taskToken)
// Verify Permissions
testCtx := APITestContext{
Session: emptyTestSession(t),
Token: taskToken,
Username: user2.Name,
Reponame: repo.Name,
}
// 1. Try to Write (Create File) - Should Fail (403) because Max is Read
testCtx.ExpectedCode = http.StatusForbidden
t.Run("Fail to Create File (Max Clamping)", doAPICreateFile(testCtx, "clamping.txt", &structs.CreateFileOptions{
ContentBase64: base64.StdEncoding.EncodeToString([]byte("test")),
})) }))
// 2. Try to Read (Get Repository) - Should Succeed (200)
testCtx.ExpectedCode = http.StatusOK
t.Run("Get Repository (Read Allowed)", doAPIGetRepository(testCtx, func(t *testing.T, r structs.Repository) {
assert.Equal(t, repo.Name, r.Name)
}))
})
}
func TestActionsTokenPackagePermission(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
// Create Repo
apiRepo := createActionsTestRepo(t, token, "repo-package-perm", false)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
defer doAPIDeleteRepository(httpContext)(t)
// Mock Runner
runner := newMockRunner()
runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
// Set Config: Custom Mode, Max Packages = Write
// This should implied Default Packages = Write (because Custom defaults to Max)
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", repo.OwnerName, repo.Name), map[string]string{
"token_permission_mode": "custom",
"max_packages": "write",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// Create workflow with NO explicit permissions (inherits Default)
wfTreePath := ".gitea/workflows/package.yml"
wfFileContent := `name: Package
on: [push]
jobs:
job-package:
runs-on: ubuntu-latest
steps:
- run: echo test
`
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
createWorkflowFile(t, token, user2.Name, repo.Name, wfTreePath, opts)
// Fetch task
runnerTask := runner.fetchTask(t)
taskToken := runnerTask.Secrets["GITEA_TOKEN"]
require.NotEmpty(t, taskToken)
// Verify Package Upload Access
packageName := "test-pkg"
packageVersion := "1.0.0"
writePackageURL := fmt.Sprintf("/api/packages/%s/generic/%s/%s/test.bin", user2.Name, packageName, packageVersion)
uploadReq := NewRequestWithBody(t, "PUT", writePackageURL, bytes.NewReader([]byte{1, 2, 3})).
AddTokenAuth(taskToken)
// Should Succeed (201)
MakeRequest(t, uploadReq, http.StatusCreated)
}) })
} }