0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-02-10 07:07:04 +01: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{}
if val == "" {
// Return defaults if no config exists
cfg.AllowCrossRepoAccess = true
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 owner of the task repo is a collaborative owner of the current repo.
// 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
perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser())
if err != nil {
return perm, err
if !isSameOrg {
return perm, nil
}
perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead)
return perm, nil
}
// Cross-repo access is always read-only
perm.SetUnitsWithDefaultAccessMode(repo.Units, perm_model.AccessModeRead)
return perm, nil

View File

@ -303,9 +303,6 @@ type ActionsConfig struct {
CollaborativeOwnerIDs []int64
// TokenPermissionMode defines the default permission mode (permissive, restricted, or custom)
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.
// Workflow YAML "permissions" keywords can reduce permissions but never exceed this ceiling.
MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
@ -366,9 +363,8 @@ func (cfg *ActionsConfig) GetEffectiveTokenPermissions(isForkPullRequest bool) A
return ForkPullRequestPermissions()
}
// Use custom default permissions if set
if cfg.DefaultTokenPermissions != nil {
return *cfg.DefaultTokenPermissions
if cfg.GetTokenPermissionMode() == ActionsTokenPermissionModeCustom {
return cfg.GetMaxTokenPermissions()
}
// Otherwise use mode-based defaults

View File

@ -3731,6 +3731,7 @@
"git.filemode.executable_file": "Executable",
"git.filemode.symbolic_link": "Symlink",
"git.filemode.submodule": "Submodule",
"org.repos.none": "No repositories.",
"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.mode": "Default Token Permissions",
@ -3744,7 +3745,7 @@
"actions.general.token_permissions.access_read": "Read",
"actions.general.token_permissions.access_write": "Write",
"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.description": "Issues and related comments, assignees, labels, and milestones.",
"actions.general.token_permissions.pull_requests": "Pull Requests",

View File

@ -52,10 +52,7 @@
</a>
</div>
<div class="flex-item-trailing">
<form method="post" action="{{$.Link}}/allowed_repos/remove?repo_id={{.ID}}">
{{$.CsrfTokenHtml}}
<button type="submit" class="ui red small button">{{ctx.Locale.Tr "remove"}}</button>
</form>
<button class="ui red small button" type="submit" formaction="{{$.Link}}/allowed_repos/remove?repo_id={{.ID}}">{{ctx.Locale.Tr "remove"}}</button>
</div>
</div>
{{else}}
@ -69,15 +66,14 @@
<h5 class="ui header">
{{ctx.Locale.Tr "actions.general.token_permissions.add_repo"}}
</h5>
<form class="ui form tw-flex" action="{{$.Link}}/allowed_repos/add" method="post">
{{.CsrfTokenHtml}}
<div class="ui form tw-flex">
<div data-global-init="initSearchRepoBox" data-uid="{{.Org.AsUser.ID}}" class="ui search tw-flex-1">
<div class="ui input">
<input class="prompt" name="repo_name" placeholder="{{ctx.Locale.Tr "search.search_repo"}}" autocomplete="off" required>
</div>
</div>
<button class="ui green button tw-ml-2">{{ctx.Locale.Tr "add"}}</button>
</form>
<button class="ui green button tw-ml-2" type="submit" formaction="{{$.Link}}/allowed_repos/add">{{ctx.Locale.Tr "add"}}</button>
</div>
</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")
actionsCfg := actionsUnit.ActionsConfig()
actionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionMode(mode)
actionsCfg.DefaultTokenPermissions = nil // Ensure no custom permissions override the mode
actionsCfg.MaxTokenPermissions = nil // Ensure no max permissions interfere
// Update the config
actionsUnit.Config = actionsCfg
@ -265,51 +264,120 @@ func testActionsTokenPermissionsMode(u *url.URL, mode string, expectReadOnly boo
func TestActionsTokenPermissionsClamping(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
httpContext := NewAPITestContext(t, "user2", "repo-clamping", auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
t.Run("Create Repository", doAPICreateRepository(httpContext, false, func(t *testing.T, repository structs.Repository) {
// Enable Actions unit with Clamping Config
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)
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 Task and Token
task := &actions_model.ActionTask{
RepoID: repository.ID,
Status: actions_model.StatusRunning,
IsForkPullRequest: false,
}
require.NoError(t, task.GenerateToken())
require.NoError(t, db.Insert(t.Context(), task))
// Create Repo
apiRepo := createActionsTestRepo(t, token, "repo-clamping", 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)
// Verify Token Permissions
session := emptyTestSession(t)
testCtx := APITestContext{
Session: session,
Token: task.Token,
Username: "user2",
Reponame: "repo-clamping",
}
// Mock Runner
runner := newMockRunner()
runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)
// 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")),
}))
// Set Clamping Config: Custom Mode (Default=Max), Max Code = Read
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_code": "read",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// 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-clamping", r.Name)
}))
// Create workflow requesting Write
wfTreePath := ".gitea/workflows/clamping.yml"
wfFileContent := `name: Clamping
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)
})
}