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