mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-24 01:52:51 +02:00
Backport #36659 by @lunny Draft release and it's attachments need a write permission to access. Signed-off-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
3422318545
commit
e7fca90a78
@ -153,3 +153,16 @@
|
|||||||
download_count: 0
|
download_count: 0
|
||||||
size: 0
|
size: 0
|
||||||
created_unix: 946684800
|
created_unix: 946684800
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 13
|
||||||
|
uuid: a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a23
|
||||||
|
repo_id: 1
|
||||||
|
issue_id: 0
|
||||||
|
release_id: 4
|
||||||
|
uploader_id: 2
|
||||||
|
comment_id: 0
|
||||||
|
name: draft-attach
|
||||||
|
download_count: 0
|
||||||
|
size: 0
|
||||||
|
created_unix: 946684800
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -21,6 +22,28 @@ import (
|
|||||||
release_service "code.gitea.io/gitea/services/release"
|
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 {
|
||||||
|
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit.TypeReleases) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hasRepoWriteScope(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// GetRelease get a single release of a repository
|
// GetRelease get a single release of a repository
|
||||||
func GetRelease(ctx *context.APIContext) {
|
func GetRelease(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/releases/{id} repository repoGetRelease
|
// swagger:operation GET /repos/{owner}/{repo}/releases/{id} repository repoGetRelease
|
||||||
@ -62,6 +85,15 @@ func GetRelease(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if release.IsDraft { // only the users with write access can see draft releases
|
||||||
|
if !canAccessDraftRelease(ctx) {
|
||||||
|
if !ctx.Written() {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := release.LoadAttributes(ctx); err != nil {
|
if err := release.LoadAttributes(ctx); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@ -151,9 +183,13 @@ func ListReleases(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
listOptions := utils.GetListOptions(ctx)
|
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{
|
opts := repo_model.FindReleasesOptions{
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
IncludeDrafts: ctx.Repo.AccessMode >= perm.AccessModeWrite || ctx.Repo.UnitAccessMode(unit.TypeReleases) >= perm.AccessModeWrite,
|
IncludeDrafts: includeDrafts,
|
||||||
IncludeTags: false,
|
IncludeTags: false,
|
||||||
IsDraft: ctx.FormOptionalBool("draft"),
|
IsDraft: ctx.FormOptionalBool("draft"),
|
||||||
IsPreRelease: ctx.FormOptionalBool("pre-release"),
|
IsPreRelease: ctx.FormOptionalBool("pre-release"),
|
||||||
|
|||||||
@ -34,6 +34,14 @@ func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
|
|||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if release.IsDraft {
|
||||||
|
if !canAccessDraftRelease(ctx) {
|
||||||
|
if !ctx.Written() {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +149,14 @@ func ListReleaseAttachments(ctx *context.APIContext) {
|
|||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if release.IsDraft {
|
||||||
|
if !canAccessDraftRelease(ctx) {
|
||||||
|
if !ctx.Written() {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := release.LoadAttributes(ctx); err != nil {
|
if err := release.LoadAttributes(ctx); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -13,8 +13,11 @@ import (
|
|||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
||||||
@ -38,3 +41,36 @@ func TestAPIEditReleaseAttachmentWithUnallowedFile(t *testing.T) {
|
|||||||
|
|
||||||
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIDraftReleaseAttachmentAccess(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 13})
|
||||||
|
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: attachment.ReleaseID})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
|
||||||
|
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
reader := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
listURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", repoOwner.Name, repo.Name, release.ID)
|
||||||
|
getURL := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", repoOwner.Name, repo.Name, release.ID, attachment.ID)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", listURL), http.StatusNotFound)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", getURL), http.StatusNotFound)
|
||||||
|
|
||||||
|
readerToken := getUserToken(t, reader.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||||
|
|
||||||
|
ownerReadToken := getUserToken(t, repoOwner.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(ownerReadToken), http.StatusNotFound)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(ownerReadToken), http.StatusNotFound)
|
||||||
|
ownerToken := getUserToken(t, repoOwner.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", listURL).AddTokenAuth(ownerToken), http.StatusOK)
|
||||||
|
var attachments []*api.Attachment
|
||||||
|
DecodeJSON(t, resp, &attachments)
|
||||||
|
if assert.Len(t, attachments, 1) {
|
||||||
|
assert.Equal(t, attachment.ID, attachments[0].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", getURL).AddTokenAuth(ownerToken), http.StatusOK)
|
||||||
|
}
|
||||||
|
|||||||
@ -29,12 +29,12 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIListReleases(t *testing.T) {
|
func TestAPIListReleasesWithWriteToken(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository)
|
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
|
||||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
@ -81,6 +81,76 @@ func TestAPIListReleases(t *testing.T) {
|
|||||||
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
|
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIListReleasesWithReadToken(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name))
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
var apiReleases []*api.Release
|
||||||
|
DecodeJSON(t, resp, &apiReleases)
|
||||||
|
if assert.Len(t, apiReleases, 2) {
|
||||||
|
for _, release := range apiReleases {
|
||||||
|
switch release.ID {
|
||||||
|
case 1:
|
||||||
|
assert.False(t, release.IsDraft)
|
||||||
|
assert.False(t, release.IsPrerelease)
|
||||||
|
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/1/assets"), release.UploadURL)
|
||||||
|
case 5:
|
||||||
|
assert.False(t, release.IsDraft)
|
||||||
|
assert.True(t, release.IsPrerelease)
|
||||||
|
assert.True(t, strings.HasSuffix(release.UploadURL, "/api/v1/repos/user2/repo1/releases/5/assets"), release.UploadURL)
|
||||||
|
default:
|
||||||
|
assert.NoError(t, fmt.Errorf("unexpected release: %v", release))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test filter
|
||||||
|
testFilterByLen := func(auth bool, query url.Values, expectedLength int, msgAndArgs ...string) {
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
if auth {
|
||||||
|
req.AddTokenAuth(token)
|
||||||
|
}
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiReleases)
|
||||||
|
assert.Len(t, apiReleases, expectedLength, msgAndArgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
testFilterByLen(false, url.Values{"draft": {"true"}}, 0, "anon should not see drafts")
|
||||||
|
testFilterByLen(true, url.Values{"draft": {"true"}}, 0, "repo owner with read token should not see drafts")
|
||||||
|
testFilterByLen(true, url.Values{"draft": {"false"}}, 2, "exclude drafts")
|
||||||
|
testFilterByLen(true, url.Values{"draft": {"false"}, "pre-release": {"false"}}, 1, "exclude drafts and pre-releases")
|
||||||
|
testFilterByLen(true, url.Values{"pre-release": {"true"}}, 1, "only get pre-release")
|
||||||
|
testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetDraftRelease(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
release := unittest.AssertExistsAndLoadBean(t, &repo_model.Release{ID: 4})
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
reader := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d", owner.Name, repo.Name, release.ID)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", urlStr), http.StatusNotFound)
|
||||||
|
|
||||||
|
readerToken := getUserToken(t, reader.LowerName, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", urlStr).AddTokenAuth(readerToken), http.StatusNotFound)
|
||||||
|
|
||||||
|
ownerToken := getUserToken(t, owner.LowerName, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", urlStr).AddTokenAuth(ownerToken), http.StatusOK)
|
||||||
|
var apiRelease api.Release
|
||||||
|
DecodeJSON(t, resp, &apiRelease)
|
||||||
|
assert.Equal(t, release.Title, apiRelease.Title)
|
||||||
|
}
|
||||||
|
|
||||||
func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release {
|
func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release {
|
||||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
|
||||||
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user