Fix issue label deletion with Actions tokens (#37013)

Use shared repo permission resolution for Actions task users in issue
label remove and clear paths, and add a regression test for deleting
issue labels with a Gitea Actions token.

This fixes issue label deletion when the request is authenticated with a
Gitea Actions token.
Fixes #37011 

The bug was that the delete path re-resolved repository permissions
using the normal user permission helper, which does not handle Actions
task users. As a result, `DELETE
/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{id}` could return
`500` for Actions tokens even though label listing and label addition
worked.

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
Nicolas
2026-03-29 11:21:14 +02:00
committed by GitHub
parent a1b0bffd0c
commit db7eb4d51b
61 changed files with 268 additions and 181 deletions
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -284,6 +285,65 @@ func TestActionsCrossRepoAccess(t *testing.T) {
})
}
func TestActionsJobTokenPermissions(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("WriteIssue", TestActionsJobTokenPermissionsWriteIssue)
}
func TestActionsJobTokenPermissionsWriteIssue(t *testing.T) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
require.Equal(t, repo.ID, task.RepoID)
require.NoError(t, db.Insert(t.Context(), &repo_model.RepoUnit{
RepoID: repo.ID,
Type: unit_model.TypeActions,
Config: &repo_model.ActionsConfig{},
}))
repoActionsUnit := repo.MustGetUnit(t.Context(), unit_model.TypeActions)
repoActionsCfg := repoActionsUnit.ActionsConfig()
repoActionsCfg.OverrideOwnerConfig = true
repoActionsCfg.TokenPermissionMode = repo_model.ActionsTokenPermissionModePermissive
repoActionsCfg.MaxTokenPermissions = nil
require.NoError(t, repo_model.UpdateRepoUnitConfig(t.Context(), repoActionsUnit))
require.NoError(t, task.GenerateToken())
task.Status = actions_model.StatusRunning
require.NoError(t, actions_model.UpdateTask(t.Context(), task, "token_hash", "token_salt", "token_last_eight", "status"))
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository)
labelURL := fmt.Sprintf("/api/v1/repos/%s/%s/labels", user.Name, repo.Name)
req := NewRequestWithJSON(t, "POST", labelURL, &structs.CreateLabelOption{
Name: "task-label",
Color: "0e8a16",
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
label := DecodeJSON(t, resp, &structs.Label{})
issueURL := fmt.Sprintf("/api/v1/repos/%s/%s/issues", user.Name, repo.Name)
req = NewRequestWithJSON(t, "POST", issueURL, &structs.CreateIssueOption{
Title: "issue for actions token label deletion",
}).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusCreated)
issue := DecodeJSON(t, resp, &structs.Issue{})
taskToken := task.Token
require.NotEmpty(t, taskToken)
issueLabelsURL := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", user.Name, repo.Name, issue.Index)
req = NewRequestWithJSON(t, "POST", issueLabelsURL, &structs.IssueLabelsOption{
Labels: []any{label.ID},
}).AddTokenAuth(taskToken)
MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%d", issueLabelsURL, label.ID)).AddTokenAuth(taskToken)
MakeRequest(t, req, http.StatusNoContent)
}
func createActionTask(t *testing.T, repoID int64, isFork bool) *actions_model.ActionTask {
job := &actions_model.ActionRunJob{
RepoID: repoID,
+1 -1
View File
@@ -498,7 +498,7 @@ func TestAPIPullReviewStayDismissed(t *testing.T) {
pullIssue.ID, user8.ID, 1, 1, 2, false)
// user8 dismiss review
permUser8, err := access_model.GetUserRepoPermission(t.Context(), pullIssue.Repo, user8)
permUser8, err := access_model.GetIndividualUserRepoPermission(t.Context(), pullIssue.Repo, user8)
assert.NoError(t, err)
_, err = issue_service.ReviewRequest(t.Context(), pullIssue, user8, &permUser8, user8, false)
assert.NoError(t, err)