Fix release draft access check logic (#36720)

1. remove hasRepoWriteScope to avoid abuse
2. clarify "ctx.Written" behavior
3. merge "read-only" tests to slightly improve performance
This commit is contained in:
wxiaoguang
2026-02-26 04:59:29 +08:00
committed by GitHub
parent 9ae28b6f39
commit 840cf68c3e
4 changed files with 38 additions and 65 deletions

View File

@@ -10,7 +10,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/git"
@@ -22,26 +21,19 @@ import (
release_service "code.gitea.io/gitea/services/release"
)
func hasRepoWriteScope(ctx *context.APIContext) bool {
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
if ctx.Data["IsApiToken"] != true || !ok {
return true
}
requiredScopes := auth_model.GetRequiredScopes(auth_model.Write, auth_model.AccessTokenScopeCategoryRepository)
allow, err := scope.HasScope(requiredScopes...)
if err != nil {
ctx.APIError(http.StatusForbidden, "checking scope failed: "+err.Error())
return false
}
return allow
}
func canAccessDraftRelease(ctx *context.APIContext) bool {
func canAccessReleaseDraft(ctx *context.APIContext) bool {
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit.TypeReleases) {
return false
}
return hasRepoWriteScope(ctx)
if ctx.Data["IsApiToken"] != true {
// not API token request, the request is from a user session with write access
return true
}
// the request is from an access token with scope
scope := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
requiredScopes := auth_model.GetRequiredScopes(auth_model.Write, auth_model.AccessTokenScopeCategoryRepository)
allow, _ := scope.HasScope(requiredScopes...) // err (invalid token) can be safely ignored
return allow
}
// GetRelease get a single release of a repository
@@ -85,13 +77,9 @@ func GetRelease(ctx *context.APIContext) {
return
}
if release.IsDraft { // only the users with write access can see draft releases
if !canAccessDraftRelease(ctx) {
if !ctx.Written() {
ctx.APIErrorNotFound()
}
return
}
if release.IsDraft && !canAccessReleaseDraft(ctx) { // only the users with write access can see draft releases
ctx.APIErrorNotFound()
return
}
if err := release.LoadAttributes(ctx); err != nil {
@@ -182,14 +170,12 @@ func ListReleases(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
listOptions := utils.GetListOptions(ctx)
includeDrafts := (ctx.Repo.AccessMode >= perm.AccessModeWrite || ctx.Repo.UnitAccessMode(unit.TypeReleases) >= perm.AccessModeWrite) && hasRepoWriteScope(ctx)
if ctx.Written() {
return
}
opts := repo_model.FindReleasesOptions{
ListOptions: listOptions,
IncludeDrafts: includeDrafts,
IncludeDrafts: canAccessReleaseDraft(ctx),
IncludeTags: false,
IsDraft: ctx.FormOptionalBool("draft"),
IsPreRelease: ctx.FormOptionalBool("pre-release"),

View File

@@ -34,13 +34,9 @@ func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
ctx.APIErrorNotFound()
return false
}
if release.IsDraft {
if !canAccessDraftRelease(ctx) {
if !ctx.Written() {
ctx.APIErrorNotFound()
}
return false
}
if release.IsDraft && !canAccessReleaseDraft(ctx) {
ctx.APIErrorNotFound()
return false
}
return true
}
@@ -149,13 +145,9 @@ func ListReleaseAttachments(ctx *context.APIContext) {
ctx.APIErrorNotFound()
return
}
if release.IsDraft {
if !canAccessDraftRelease(ctx) {
if !ctx.Written() {
ctx.APIErrorNotFound()
}
return
}
if release.IsDraft && !canAccessReleaseDraft(ctx) {
ctx.APIErrorNotFound()
return
}
if err := release.LoadAttributes(ctx); err != nil {
ctx.APIErrorInternal(err)