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:
parent
f7fc8792d5
commit
feb791cccf
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user