Fix comment permissions () ()

backport 

This PR will fix some missed checks for private repositories' data on
web routes and API routes.

(cherry picked from commit dfd511faf3)
This commit is contained in:
Lunny Xiao 2023-11-26 07:21:41 +08:00 committed by Loïc Dachary
parent a7a9876dd4
commit db0d71ec0f
No known key found for this signature in database
GPG key ID: 992D23B392F9E4F2
35 changed files with 422 additions and 109 deletions

View file

@ -93,9 +93,9 @@ func CountUserGPGKeys(userID int64) (int64, error) {
}
// GetGPGKeyByID returns public key by given ID.
func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
func GetGPGKeyForUserByID(ownerID, keyID int64) (*GPGKey, error) {
key := new(GPGKey)
has, err := db.GetEngine(db.DefaultContext).ID(keyID).Get(key)
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND owner_id=?", keyID, ownerID).Get(key)
if err != nil {
return nil, err
} else if !has {
@ -225,7 +225,7 @@ func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
// DeleteGPGKey deletes GPG key information in database.
func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
key, err := GetGPGKeyByID(id)
key, err := GetGPGKeyForUserByID(doer.ID, id)
if err != nil {
if IsErrGPGKeyNotExist(err) {
return nil
@ -233,11 +233,6 @@ func DeleteGPGKey(doer *user_model.User, id int64) (err error) {
return fmt.Errorf("GetPublicKeyByID: %w", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin && doer.ID != key.OwnerID {
return ErrGPGKeyAccessDenied{doer.ID, key.ID}
}
ctx, committer, err := db.TxContext(db.DefaultContext)
if err != nil {
return err

View file

@ -66,3 +66,12 @@
tree_path: "README.md"
created_unix: 946684812
invalidated: true
-
id: 8
type: 0 # comment
poster_id: 2
issue_id: 4 # in repo_id 2
content: "comment in private pository"
created_unix: 946684811
updated_unix: 946684811

View file

@ -61,7 +61,7 @@
priority: 0
is_closed: true
is_pull: false
num_comments: 0
num_comments: 1
created_unix: 946684830
updated_unix: 978307200
is_locked: false

View file

@ -1014,6 +1014,7 @@ type FindCommentsOptions struct {
Type CommentType
IssueIDs []int64
Invalidated util.OptionalBool
IsPull util.OptionalBool
}
// ToConds implements FindOptions interface
@ -1048,6 +1049,9 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
if !opts.Invalidated.IsNone() {
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
}
if opts.IsPull != util.OptionalBoolNone {
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
}
return cond
}
@ -1055,7 +1059,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
comments := make([]*Comment, 0, 10)
sess := db.GetEngine(ctx).Where(opts.ToConds())
if opts.RepoID > 0 {
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
}

View file

@ -218,9 +218,9 @@ func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistor
}
// GetIssueContentHistoryAndPrev get a history and the previous non-deleted history (to compare)
func GetIssueContentHistoryAndPrev(dbCtx context.Context, id int64) (history, prevHistory *ContentHistory, err error) {
func GetIssueContentHistoryAndPrev(dbCtx context.Context, issueID, id int64) (history, prevHistory *ContentHistory, err error) {
history = &ContentHistory{}
has, err := db.GetEngine(dbCtx).ID(id).Get(history)
has, err := db.GetEngine(dbCtx).Where("id=? AND issue_id=?", id, issueID).Get(history)
if err != nil {
log.Error("failed to get issue content history %v. err=%v", id, err)
return nil, nil, err

View file

@ -58,13 +58,13 @@ func TestContentHistory(t *testing.T) {
hasHistory2, _ := issues_model.HasIssueContentHistory(dbCtx, 10, 1)
assert.False(t, hasHistory2)
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
assert.EqualValues(t, 6, h6.ID)
assert.EqualValues(t, 5, h6Prev.ID)
// soft-delete
_ = issues_model.SoftDeleteIssueContentHistory(dbCtx, 5)
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
assert.EqualValues(t, 6, h6.ID)
assert.EqualValues(t, 4, h6Prev.ID)

View file

@ -306,6 +306,18 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) {
return p, nil
}
// GetProjectForRepoByID returns the projects in a repository
func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) {
p := new(Project)
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(p)
if err != nil {
return nil, err
} else if !has {
return nil, ErrProjectNotExist{ID: id}
}
return p, nil
}
// UpdateProject updates project properties
func UpdateProject(ctx context.Context, p *Project) error {
if !IsCardTypeValid(p.CardType) {

View file

@ -201,6 +201,21 @@ func GetReleaseByID(ctx context.Context, id int64) (*Release, error) {
return rel, nil
}
// GetReleaseForRepoByID returns release with given ID.
func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, error) {
rel := new(Release)
has, err := db.GetEngine(ctx).
Where("id=? AND repo_id=?", id, repoID).
Get(rel)
if err != nil {
return nil, err
} else if !has {
return nil, ErrReleaseNotExist{id, ""}
}
return rel, nil
}
// FindReleasesOptions describes the conditions to Find releases
type FindReleasesOptions struct {
db.ListOptions

View file

@ -392,39 +392,40 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
return db.Insert(ctx, ws)
}
// getWebhook uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func getWebhook(bean *Webhook) (*Webhook, error) {
has, err := db.GetEngine(db.DefaultContext).Get(bean)
// GetWebhookByID returns webhook of repository by given ID.
func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
bean := new(Webhook)
has, err := db.GetEngine(ctx).ID(id).Get(bean)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{ID: bean.ID}
return nil, ErrWebhookNotExist{ID: id}
}
return bean, nil
}
// GetWebhookByID returns webhook of repository by given ID.
func GetWebhookByID(id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
})
}
// GetWebhookByRepoID returns webhook of repository by given ID.
func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
RepoID: repoID,
})
webhook := new(Webhook)
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{ID: id}
}
return webhook, nil
}
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) {
return getWebhook(&Webhook{
ID: id,
OwnerID: ownerID,
})
webhook := new(Webhook)
has, err := db.GetEngine(db.DefaultContext).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
if err != nil {
return nil, err
} else if !has {
return nil, ErrWebhookNotExist{ID: id}
}
return webhook, nil
}
// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
@ -482,20 +483,20 @@ func UpdateWebhookLastStatus(w *Webhook) error {
return err
}
// deleteWebhook uses argument bean as query condition,
// DeleteWebhookByID uses argument bean as query condition,
// ID must be specified and do not assign unnecessary fields.
func deleteWebhook(bean *Webhook) (err error) {
func DeleteWebhookByID(id int64) (err error) {
ctx, committer, err := db.TxContext(db.DefaultContext)
if err != nil {
return err
}
defer committer.Close()
if count, err := db.DeleteByBean(ctx, bean); err != nil {
if count, err := db.DeleteByID(ctx, id, new(Webhook)); err != nil {
return err
} else if count == 0 {
return ErrWebhookNotExist{ID: bean.ID}
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: bean.ID}); err != nil {
return ErrWebhookNotExist{ID: id}
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
return err
}
@ -504,16 +505,16 @@ func deleteWebhook(bean *Webhook) (err error) {
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
func DeleteWebhookByRepoID(repoID, id int64) error {
return deleteWebhook(&Webhook{
ID: id,
RepoID: repoID,
})
if _, err := GetWebhookByRepoID(repoID, id); err != nil {
return err
}
return DeleteWebhookByID(id)
}
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
func DeleteWebhookByOwnerID(ownerID, id int64) error {
return deleteWebhook(&Webhook{
ID: id,
OwnerID: ownerID,
})
if _, err := GetWebhookByOwnerID(ownerID, id); err != nil {
return err
}
return DeleteWebhookByID(id)
}

View file

@ -1154,8 +1154,8 @@ func Routes(ctx gocontext.Context) *web.Route {
m.Group("/{username}/{reponame}", func() {
m.Group("/issues", func() {
m.Combo("").Get(repo.ListIssues).
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
m.Get("/pinned", repo.ListPinnedIssues)
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
m.Group("/comments", func() {
m.Get("", repo.ListRepoIssueComments)
m.Group("/{id}", func() {

View file

@ -452,6 +452,24 @@ func ListIssues(ctx *context.APIContext) {
isPull = util.OptionalBoolNone
}
if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(isPull.IsTrue()) {
ctx.NotFound()
return
}
if isPull == util.OptionalBoolNone {
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
if !canReadIssues && !canReadPulls {
ctx.NotFound()
return
} else if !canReadIssues {
isPull = util.OptionalBoolTrue
} else if !canReadPulls {
isPull = util.OptionalBoolFalse
}
}
// FIXME: we should be more efficient here
createdByID := getUserIDForFilter(ctx, "created_by")
if ctx.Written() {
@ -562,6 +580,10 @@ func GetIssue(ctx *context.APIContext) {
}
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
}

View file

@ -12,9 +12,11 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
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/context"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/convert"
@ -69,6 +71,11 @@ func ListIssueComments(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
issue.Repo = ctx.Repo.Repository
opts := &issues_model.FindCommentsOptions{
@ -265,12 +272,27 @@ func ListRepoIssueComments(ctx *context.APIContext) {
return
}
var isPull util.OptionalBool
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
if canReadIssue && canReadPull {
isPull = util.OptionalBoolNone
} else if canReadIssue {
isPull = util.OptionalBoolFalse
} else if canReadPull {
isPull = util.OptionalBoolTrue
} else {
ctx.NotFound()
return
}
opts := &issues_model.FindCommentsOptions{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
Type: issues_model.CommentTypeComment,
Since: since,
Before: before,
IsPull: isPull,
}
comments, err := issues_model.FindComments(ctx, opts)
@ -357,6 +379,11 @@ func CreateIssueComment(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
ctx.NotFound()
return
}
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
return
@ -430,6 +457,11 @@ func GetIssueComment(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
if comment.Type != issues_model.CommentTypeComment {
ctx.Status(http.StatusNoContent)
return
@ -548,7 +580,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Status(http.StatusForbidden)
return
}
@ -651,7 +693,17 @@ func deleteIssueComment(ctx *context.APIContext) {
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound)
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Status(http.StatusForbidden)
return
} else if comment.Type != issues_model.CommentTypeComment {

View file

@ -325,6 +325,10 @@ func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
return nil
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
return nil
}
comment.Issue.Repo = ctx.Repo.Repository
return comment

View file

@ -61,6 +61,12 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
if err := comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
@ -186,9 +192,19 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
return
}
err = comment.LoadIssue(ctx)
if err != nil {
if err = comment.LoadIssue(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound()
return
}
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {

View file

@ -155,6 +155,12 @@ func GetDeployKey(ctx *context.APIContext) {
return
}
// this check make it more consistent
if key.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return
}
if err = key.GetContent(); err != nil {
ctx.Error(http.StatusInternalServerError, "GetContent", err)
return

View file

@ -49,13 +49,12 @@ func GetRelease(ctx *context.APIContext) {
// "$ref": "#/responses/notFound"
id := ctx.ParamsInt64(":id")
release, err := repo_model.GetReleaseByID(ctx, id)
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
ctx.NotFound()
return
}
@ -313,13 +312,12 @@ func EditRelease(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditReleaseOption)
id := ctx.ParamsInt64(":id")
rel, err := repo_model.GetReleaseByID(ctx, id)
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
ctx.NotFound()
return
}
@ -391,17 +389,16 @@ func DeleteRelease(ctx *context.APIContext) {
// "$ref": "#/responses/empty"
id := ctx.ParamsInt64(":id")
rel, err := repo_model.GetReleaseByID(ctx, id)
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
return
}
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
ctx.NotFound()
return
}
if err := release_service.DeleteReleaseByID(ctx, id, ctx.Doer, false); err != nil {
if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View file

@ -17,6 +17,23 @@ import (
"code.gitea.io/gitea/services/convert"
)
func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound()
return false
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
return false
}
if release.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound()
return false
}
return true
}
// GetReleaseAttachment gets a single attachment of the release
func GetReleaseAttachment(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
@ -52,6 +69,10 @@ func GetReleaseAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/Attachment"
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
@ -170,13 +191,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
release, err := repo_model.GetReleaseByID(ctx, releaseID)
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound()
return
}
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
@ -197,7 +212,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
attach, err := attachment.UploadAttachment(file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
Name: filename,
UploaderID: ctx.Doer.ID,
RepoID: release.RepoID,
RepoID: ctx.Repo.Repository.ID,
ReleaseID: releaseID,
})
if err != nil {
@ -256,6 +271,10 @@ func EditReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {
@ -318,6 +337,10 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
// Check if release exists an load release
releaseID := ctx.ParamsInt64(":id")
if !checkReleaseMatchRepo(ctx, releaseID) {
return
}
attachID := ctx.ParamsInt64(":attachment_id")
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
if err != nil {

View file

@ -112,7 +112,7 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
return
}
if err = releaseservice.DeleteReleaseByID(ctx, release.ID, ctx.Doer, false); err != nil {
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View file

@ -264,7 +264,7 @@ func DeleteTag(ctx *context.APIContext) {
return
}
if err = releaseservice.DeleteReleaseByID(ctx, tag.ID, ctx.Doer, true); err != nil {
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
return

View file

@ -337,6 +337,10 @@ func GetOauth2Application(ctx *context.APIContext) {
}
return
}
if app.UID != ctx.Doer.ID {
ctx.NotFound()
return
}
app.ClientSecret = ""

View file

@ -110,7 +110,7 @@ func GetGPGKey(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
key, err := asymkey_model.GetGPGKeyByID(ctx.ParamsInt64(":id"))
key, err := asymkey_model.GetGPGKeyForUserByID(ctx.Doer.ID, ctx.ParamsInt64(":id"))
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()

View file

@ -62,6 +62,11 @@ func GetHook(ctx *context.APIContext) {
return
}
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
ctx.NotFound()
return
}
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
if err != nil {
ctx.InternalServerError(err)

View file

@ -2971,6 +2971,11 @@ func UpdateCommentContent(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Error(http.StatusForbidden)
return
@ -3037,6 +3042,11 @@ func DeleteComment(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
ctx.Error(http.StatusForbidden)
return
@ -3163,6 +3173,11 @@ func ChangeCommentReaction(ctx *context.Context) {
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull)) {
if log.IsTrace() {
if ctx.IsSigned {
@ -3306,6 +3321,21 @@ func GetCommentAttachments(ctx *context.Context) {
return
}
if err := comment.LoadIssue(ctx); err != nil {
ctx.NotFoundOrServerError("LoadIssue", issues_model.IsErrIssueNotExist, err)
return
}
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
ctx.NotFound("CanReadIssuesOrPulls", issues_model.ErrCommentNotExist{})
return
}
if !comment.Type.HasAttachmentSupport() {
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
return

View file

@ -118,7 +118,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
}
historyID := ctx.FormInt64("history_id")
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, historyID)
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, issue.ID, historyID)
if err != nil {
ctx.JSON(http.StatusNotFound, map[string]any{
"message": "Can not find the content history",

View file

@ -467,7 +467,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)

View file

@ -592,7 +592,31 @@ func DeleteTag(ctx *context.Context) {
}
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil {
redirect := func() {
if isDelTag {
ctx.JSON(http.StatusOK, map[string]any{
"redirect": ctx.Repo.RepoLink + "/tags",
})
return
}
ctx.JSON(http.StatusOK, map[string]any{
"redirect": ctx.Repo.RepoLink + "/releases",
})
}
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
if err != nil {
if repo_model.IsErrReleaseNotExist(err) {
ctx.NotFound("GetReleaseForRepoByID", err)
} else {
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
redirect()
}
return
}
if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
if models.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
} else {
@ -606,14 +630,5 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
}
}
if isDelTag {
ctx.JSON(http.StatusOK, map[string]any{
"redirect": ctx.Repo.RepoLink + "/tags",
})
return
}
ctx.JSON(http.StatusOK, map[string]any{
"redirect": ctx.Repo.RepoLink + "/releases",
})
redirect()
}

View file

@ -291,17 +291,7 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
}
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, delTag bool) error {
rel, err := repo_model.GetReleaseByID(ctx, id)
if err != nil {
return fmt.Errorf("GetReleaseByID: %w", err)
}
repo, err := repo_model.GetRepositoryByID(ctx, rel.RepoID)
if err != nil {
return fmt.Errorf("GetRepositoryByID: %w", err)
}
func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *repo_model.Release, doer *user_model.User, delTag bool) error {
if delTag {
protectedTags, err := git_model.GetProtectedTags(ctx, rel.RepoID)
if err != nil {
@ -334,19 +324,19 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del
}, repository.NewPushCommits())
notification.NotifyDeleteRef(ctx, doer, repo, refName)
if err := repo_model.DeleteReleaseByID(ctx, id); err != nil {
if err := repo_model.DeleteReleaseByID(ctx, rel.ID); err != nil {
return fmt.Errorf("DeleteReleaseByID: %w", err)
}
} else {
rel.IsTag = true
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
if err := repo_model.UpdateRelease(ctx, rel); err != nil {
return fmt.Errorf("Update: %w", err)
}
}
rel.Repo = repo
if err = rel.LoadAttributes(ctx); err != nil {
if err := rel.LoadAttributes(ctx); err != nil {
return fmt.Errorf("LoadAttributes: %w", err)
}

View file

@ -34,7 +34,7 @@ import (
// Deliver deliver hook task
func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
w, err := webhook_model.GetWebhookByID(t.HookID)
w, err := webhook_model.GetWebhookByID(ctx, t.HookID)
if err != nil {
return err
}

View file

@ -35,6 +35,14 @@ func TestAPIGetCommentAttachment(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
t.Run("UnrelatedCommentID", func(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
MakeRequest(t, req, http.StatusNotFound)
})
session := loginUser(t, repoOwner.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)

View file

@ -177,12 +177,25 @@ func TestAPIEditComment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
const newCommentBody = "This is the new comment body"
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 8},
unittest.Cond("type = ?", issues_model.CommentTypeComment))
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
t.Run("UnrelatedCommentID", func(t *testing.T) {
// Using the ID of a comment that does not belong to the repository must fail
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
repoOwner.Name, repo.Name, comment.ID, token)
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
"body": newCommentBody,
})
MakeRequest(t, req, http.StatusNotFound)
})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
repoOwner.Name, repo.Name, comment.ID, token)
@ -201,12 +214,22 @@ func TestAPIEditComment(t *testing.T) {
func TestAPIDeleteComment(t *testing.T) {
defer tests.PrepareTestEnv(t)()
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 8},
unittest.Cond("type = ?", issues_model.CommentTypeComment))
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
t.Run("UnrelatedCommentID", func(t *testing.T) {
// Using the ID of a comment that does not belong to the repository must fail
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
repoOwner.Name, repo.Name, comment.ID, token)
MakeRequest(t, req, http.StatusNotFound)
})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
repoOwner.Name, repo.Name, comment.ID, token)

View file

@ -12,6 +12,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
@ -107,6 +108,26 @@ func TestAPICommentReactions(t *testing.T) {
})
MakeRequest(t, req, http.StatusOK)
t.Run("UnrelatedCommentID", func(t *testing.T) {
// Using the ID of a comment that does not belong to the repository must fail
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
repoOwner.Name, repo.Name, comment.ID, token)
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
Reaction: "+1",
})
MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
Reaction: "+1",
})
MakeRequest(t, req, http.StatusNotFound)
req = NewRequestf(t, "GET", urlStr)
MakeRequest(t, req, http.StatusNotFound)
})
// Add allowed reaction
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
Reaction: "+1",

View file

@ -72,6 +72,17 @@ func TestCreateReadOnlyDeployKey(t *testing.T) {
Content: rawKeyBody.Key,
Mode: perm.AccessModeRead,
})
// Using the ID of a key that does not belong to the repository must fail
{
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d?token=%s", repoOwner.Name, repo.Name, newDeployKey.ID, token))
MakeRequest(t, req, http.StatusOK)
session5 := loginUser(t, "user5")
token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository)
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d?token=%s", newDeployKey.ID, token5))
MakeRequest(t, req, http.StatusNotFound)
}
}
func TestCreateReadWriteDeployKey(t *testing.T) {

View file

@ -35,6 +35,6 @@ func TestNodeinfo(t *testing.T) {
assert.Equal(t, "forgejo", nodeinfo.Software.Name)
assert.Equal(t, 25, nodeinfo.Usage.Users.Total)
assert.Equal(t, 18, nodeinfo.Usage.LocalPosts)
assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
assert.Equal(t, 3, nodeinfo.Usage.LocalComments)
})
}

View file

@ -205,6 +205,56 @@ func TestIssueCommentClose(t *testing.T) {
assert.Equal(t, "Description", val)
}
func TestIssueCommentDelete(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
comment1 := "Test comment 1"
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
assert.Equal(t, comment1, comment.Content)
// Using the ID of a comment that does not belong to the repository must fail
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
"_csrf": GetCSRF(t, session, issueURL),
})
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
"_csrf": GetCSRF(t, session, issueURL),
})
session.MakeRequest(t, req, http.StatusOK)
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
}
func TestIssueCommentUpdate(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
comment1 := "Test comment 1"
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
assert.Equal(t, comment1, comment.Content)
modifiedContent := comment.Content + "MODIFIED"
// Using the ID of a comment that does not belong to the repository must fail
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
"_csrf": GetCSRF(t, session, issueURL),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
"_csrf": GetCSRF(t, session, issueURL),
"content": modifiedContent,
})
session.MakeRequest(t, req, http.StatusOK)
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
assert.Equal(t, modifiedContent, comment.Content)
}
func TestIssueReaction(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")

View file

@ -87,7 +87,7 @@ func TestMirrorPull(t *testing.T) {
release, err := repo_model.GetRelease(repo.ID, "v0.2")
assert.NoError(t, err)
assert.NoError(t, release_service.DeleteReleaseByID(ctx, release.ID, user, true))
assert.NoError(t, release_service.DeleteReleaseByID(ctx, repo, release, user, true))
ok = mirror_service.SyncPullMirror(ctx, mirror.ID)
assert.True(t, ok)