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
+1 -1
View File
@@ -169,7 +169,7 @@ func UploadPackage(ctx *context.Context) {
canWrite := repo.OwnerID == ctx.Doer.ID
if !canWrite {
perms, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perms, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
+1 -1
View File
@@ -202,7 +202,7 @@ func repoAssignment() func(ctx *context.APIContext) {
if needTwoFactor {
ctx.Repo.Permission = access_model.PermissionNoAccess()
} else {
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
ctx.Repo.Permission, err = access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+2 -2
View File
@@ -576,7 +576,7 @@ func GetTeamRepos(ctx *context.APIContext) {
}
repos := make([]*api.Repository, len(teamRepos))
for i, repo := range teamRepos {
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -628,7 +628,7 @@ func GetTeamRepo(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+1 -1
View File
@@ -291,7 +291,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
permission, err := access_model.GetIndividualUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
if err != nil {
ctx.APIErrorInternal(err)
return
+1 -1
View File
@@ -71,7 +71,7 @@ func ListForks(ctx *context.APIContext) {
apiForks := make([]*api.Repository, len(forks))
for i, fork := range forks {
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, fork, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+1 -1
View File
@@ -219,7 +219,7 @@ func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *issue
if err != nil {
return false
}
perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, user)
perm, err := access_model.GetDoerRepoPermission(ctx, c.RefRepo, user)
if err != nil {
return false
}
+3 -3
View File
@@ -102,7 +102,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -351,7 +351,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -537,7 +537,7 @@ func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository)
return &ctx.Repo.Permission
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil
+2 -2
View File
@@ -1113,7 +1113,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
}()
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
permBase, err := access_model.GetDoerRepoPermission(ctx, baseRepo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
@@ -1127,7 +1127,7 @@ func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git
// user should have permission to read headRepo's codes
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
permHead, err := access_model.GetDoerRepoPermission(ctx, headRepo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return nil, nil
+1 -1
View File
@@ -833,7 +833,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
return
}
permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
permDoer, err := access_model.GetDoerRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+2 -2
View File
@@ -221,7 +221,7 @@ func Search(ctx *context.APIContext) {
})
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.JSON(http.StatusInternalServerError, api.SearchError{
OK: false,
@@ -588,7 +588,7 @@ func GetByID(ctx *context.APIContext) {
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
+2 -2
View File
@@ -37,7 +37,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
apiRepos := make([]*api.Repository, 0, len(repos))
for i := range repos {
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repos[i], ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
return
@@ -123,7 +123,7 @@ func ListMyRepos(ctx *context.APIContext) {
ctx.APIErrorInternal(err)
return
}
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.APIErrorInternal(err)
}
+1 -1
View File
@@ -31,7 +31,7 @@ func getStarredRepos(ctx *context.APIContext, user *user_model.User, private boo
repos := make([]*api.Repository, len(starredRepos))
for i, starred := range starredRepos {
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
permission, err := access_model.GetIndividualUserRepoPermission(ctx, starred, user)
if err != nil {
return nil, err
}
+1 -1
View File
@@ -29,7 +29,7 @@ func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private boo
repos := make([]*api.Repository, len(watchedRepos))
for i, watched := range watchedRepos {
permission, err := access_model.GetUserRepoPermission(ctx, watched, user)
permission, err := access_model.GetIndividualUserRepoPermission(ctx, watched, user)
if err != nil {
return nil, 0, err
}
+1 -1
View File
@@ -192,7 +192,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
})
return
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, pusher)
perm, err := access_model.GetDoerRepoPermission(ctx, repo, pusher)
if err != nil {
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
+1 -1
View File
@@ -525,7 +525,7 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool {
return false
}
ctx.user = user
userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user)
userPerm, err := access_model.GetDoerRepoPermission(ctx, ctx.Repo.Repository, user)
if err != nil {
log.Error("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
+1 -1
View File
@@ -338,7 +338,7 @@ func ServCommand(ctx *context.PrivateContext) {
mode = perm.AccessModeRead
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, user)
perm, err := access_model.GetDoerRepoPermission(ctx, repo, user)
if err != nil {
log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err)
ctx.JSON(http.StatusInternalServerError, private.Response{
+2 -2
View File
@@ -157,9 +157,9 @@ func ServeAttachment(ctx *context.Context, uuid string) {
ctx.ServerError("GetRepositoryByID", err)
return
}
perm, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
} else {
+2 -2
View File
@@ -33,9 +33,9 @@ func prepareRecentlyPushedNewBranches(ctx *context.Context) {
opts.BaseRepo = ctx.Repo.Repository.BaseRepo
}
baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
baseRepoPerm, err := access_model.GetDoerRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
if err != nil {
log.Error("GetUserRepoPermission: %v", err)
log.Error("GetDoerRepoPermission: %v", err)
return
}
if !opts.Repo.CanContentChange() || !opts.BaseRepo.CanContentChange() {
+2 -2
View File
@@ -226,9 +226,9 @@ func ParseCompareInfo(ctx *context.Context) *git_service.CompareInfo {
// If we're not merging from the same repo:
if !isSameRepo {
// Assert ctx.Doer has permission to read headRepo's codes
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
permHead, err := access_model.GetDoerRepoPermission(ctx, headRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return nil
}
if !permHead.CanRead(unit.TypeCode) {
+8 -23
View File
@@ -22,7 +22,6 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/git/gitcmd"
"code.gitea.io/gitea/modules/gitrepo"
@@ -186,29 +185,15 @@ func httpBase(ctx *context.Context, optGitService ...string) *serviceHandler {
accessMode = perm.AccessModeRead
}
taskID, isActionsUser := user_model.GetActionsUserTaskID(ctx.Doer)
if isActionsUser {
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
if err != nil {
ctx.ServerError("GetActionsUserRepoPermission", err)
return nil
}
p, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetDoerRepoPermission", err)
return nil
}
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
} else {
p, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
return nil
}
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
if !p.CanAccess(accessMode, unitType) {
ctx.PlainText(http.StatusNotFound, "Repository not found")
return nil
}
if !isPull && repo.IsMirror {
+2 -2
View File
@@ -55,9 +55,9 @@ func AddDependency(ctx *context.Context) {
return
}
// Can ctx.Doer read issues in the dep repo?
depRepoPerm, err := access_model.GetUserRepoPermission(ctx, dep.Repo, ctx.Doer)
depRepoPerm, err := access_model.GetDoerRepoPermission(ctx, dep.Repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
if !depRepoPerm.CanReadIssuesOrPulls(dep.IsPull) {
+8 -8
View File
@@ -57,7 +57,7 @@ func roleDescriptor(ctx *context.Context, repo *repo_model.Repository, poster *u
// Guess the role of the poster in the repo by permission
perm, hasPermCache := permsCache[poster.ID]
if !hasPermCache {
perm, err = access_model.GetUserRepoPermission(ctx, repo, poster)
perm, err = access_model.GetIndividualUserRepoPermission(ctx, repo, poster)
if err != nil {
return roleDesc, err
}
@@ -145,9 +145,9 @@ func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.Depende
perm = existPerm
} else {
var err error
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
perm, err = access_model.GetDoerRepoPermission(ctx, &blocker.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return nil, nil
}
repoPerms[blocker.RepoID] = perm
@@ -192,7 +192,7 @@ func filterXRefComments(ctx *context.Context, issue *issues_model.Issue) error {
if err != nil {
return err
}
perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, c.RefRepo, ctx.Doer)
if err != nil {
return err
}
@@ -845,9 +845,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
if err := pull.LoadHeadRepo(ctx); err != nil {
log.Error("LoadHeadRepo: %v", err)
} else if pull.HeadRepo != nil {
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
if perm.CanWrite(unit.TypeCode) {
@@ -867,9 +867,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
if err := pull.LoadBaseRepo(ctx); err != nil {
log.Error("LoadBaseRepo: %v", err)
}
perm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, pull.BaseRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
if !canWriteToHeadRepo { // maintainers maybe allowed to push to head repo even if they can't write to it
+4 -4
View File
@@ -95,9 +95,9 @@ func getRepository(ctx *context.Context, repoID int64) *repo_model.Repository {
return nil
}
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return nil
}
@@ -958,9 +958,9 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
if pull.HeadRepo != nil {
if !pull.HasMerged && ctx.Doer != nil {
perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
perm, err := access_model.GetDoerRepoPermission(ctx, pull.HeadRepo, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
+1 -1
View File
@@ -101,7 +101,7 @@ func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProf
return nil, nil
}
perm, err := access_model.GetUserRepoPermission(ctx, profileDbRepo, doer)
perm, err := access_model.GetDoerRepoPermission(ctx, profileDbRepo, doer)
if err != nil {
log.Error("FindOwnerProfileReadme failed to GetRepositoryByName: %v", err)
return nil, nil
+4 -4
View File
@@ -84,9 +84,9 @@ func ListPackages(ctx *context.Context) {
continue
}
permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, pd.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
repositoryAccessMap[pd.Repository.ID] = permission.HasAnyUnitAccess()
@@ -320,9 +320,9 @@ func ViewPackageVersion(ctx *context.Context) {
hasRepositoryAccess := false
if pd.Repository != nil {
permission, err := access_model.GetUserRepoPermission(ctx, pd.Repository, ctx.Doer)
permission, err := access_model.GetDoerRepoPermission(ctx, pd.Repository, ctx.Doer)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetDoerRepoPermission", err)
return
}
hasRepositoryAccess = permission.HasAnyUnitAccess()