mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
fix: check that attachments belong to correct resource
It was possible to hijack attachments during update and create functions to another owner as permissions to check they weren't already attached to another resource and wasn't checked if it belonged to the repository that was being operated on.
This commit is contained in:
parent
68b2930caa
commit
ce0a376723
7 changed files with 93 additions and 79 deletions
|
|
@ -611,9 +611,13 @@ func (c *Comment) UpdateAttachments(ctx context.Context, uuids []string) error {
|
|||
}
|
||||
defer committer.Close()
|
||||
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
|
||||
if err := c.LoadIssue(ctx); err != nil {
|
||||
return fmt.Errorf("LoadIssue: %w", err)
|
||||
}
|
||||
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(ctx, c.Issue.RepoID, uuids, repo_model.FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, c.Issue.RepoID, err)
|
||||
}
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = c.IssueID
|
||||
|
|
@ -889,7 +893,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
// Check comment type.
|
||||
switch opts.Type {
|
||||
case CommentTypeCode:
|
||||
if err = updateAttachments(ctx, opts, comment); err != nil {
|
||||
if err := comment.UpdateAttachments(ctx, opts.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
if comment.ReviewID != 0 {
|
||||
|
|
@ -909,7 +913,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
}
|
||||
fallthrough
|
||||
case CommentTypeReview:
|
||||
if err = updateAttachments(ctx, opts, comment); err != nil {
|
||||
if err := comment.UpdateAttachments(ctx, opts.Attachments); err != nil {
|
||||
return err
|
||||
}
|
||||
case CommentTypeReopen, CommentTypeClose:
|
||||
|
|
@ -921,23 +925,6 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
|
|||
return UpdateIssueCols(ctx, opts.Issue, "updated_unix")
|
||||
}
|
||||
|
||||
func updateAttachments(ctx context.Context, opts *CreateCommentOptions, comment *Comment) error {
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
|
||||
}
|
||||
for i := range attachments {
|
||||
attachments[i].IssueID = opts.Issue.ID
|
||||
attachments[i].CommentID = comment.ID
|
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
}
|
||||
comment.Attachments = attachments
|
||||
return nil
|
||||
}
|
||||
|
||||
func createDeadlineComment(ctx context.Context, doer *user_model.User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
|
||||
var content string
|
||||
var commentType CommentType
|
||||
|
|
|
|||
|
|
@ -234,18 +234,18 @@ func AddDeletePRBranchComment(ctx context.Context, doer *user_model.User, repo *
|
|||
}
|
||||
|
||||
// UpdateIssueAttachments update attachments by UUIDs for the issue
|
||||
func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) (err error) {
|
||||
func UpdateIssueAttachments(ctx context.Context, issue *Issue, uuids []string) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, uuids)
|
||||
attachments, err := repo_model.FindRepoAttachmentsByUUID(ctx, issue.RepoID, uuids, repo_model.FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", uuids, issue.RepoID, err)
|
||||
}
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = issueID
|
||||
attachments[i].IssueID = issue.ID
|
||||
if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
|
|
@ -394,18 +394,8 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue
|
|||
return err
|
||||
}
|
||||
|
||||
if len(opts.Attachments) > 0 {
|
||||
attachments, err := repo_model.GetAttachmentsByUUIDs(ctx, opts.Attachments)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", opts.Attachments, err)
|
||||
}
|
||||
|
||||
for i := 0; i < len(attachments); i++ {
|
||||
attachments[i].IssueID = opts.Issue.ID
|
||||
if _, err = e.ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err)
|
||||
}
|
||||
}
|
||||
if err := UpdateIssueAttachments(ctx, opts.Issue, opts.Attachments); err != nil {
|
||||
return fmt.Errorf("UpdateIssueAttachments: %w", err)
|
||||
}
|
||||
if err = opts.Issue.LoadAttributes(ctx); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
|
@ -16,17 +17,30 @@ import (
|
|||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/util"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Attachment represent a attachment of issue/comment/release.
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
RepoID int64 `xorm:"INDEX"` // this should not be zero
|
||||
IssueID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
// UUID is the public identifier of the attachment, and is used during HTTP
|
||||
// requests to refer to a specific attachment.
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
// UploaderID is always set and non-zero and refers to the user that has
|
||||
// uploaded this attachment.
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"`
|
||||
// RepoID is always set and non-zero and refers to the repository where this
|
||||
// attachment was uploaded to.
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
// IssueID, ReleaseID and CommentID have multiple possible states:
|
||||
// - ReleaseID != 0 && IssueID == 0 && CommentID == 0: attached to release with id `ReleaseID`.
|
||||
// - ReleaseID == 0 && IssueID != 0 && CommentID == 0: attached to the issue with id `IssueID`.
|
||||
// - ReleaseID == 0 && IssueID != 0 && CommentID != 0: attached to comment with id `CommentID` that is in issue with id `IssueID`.
|
||||
// All other states should be considered invalid.
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
ReleaseID int64 `xorm:"INDEX"`
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Size int64 `xorm:"DEFAULT 0"`
|
||||
|
|
@ -73,6 +87,12 @@ func (a *Attachment) DownloadURL() string {
|
|||
return setting.AppURL + "attachments/" + url.PathEscape(a.UUID)
|
||||
}
|
||||
|
||||
// IsAttachedToResource returns true if this attachment is attached to a release,
|
||||
// issue or comment.
|
||||
func (a *Attachment) IsAttachedToResource() bool {
|
||||
return a.ReleaseID != 0 || a.IssueID != 0 || a.CommentID != 0
|
||||
}
|
||||
|
||||
// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
|
||||
type ErrAttachmentNotExist struct {
|
||||
ID int64
|
||||
|
|
@ -133,15 +153,37 @@ func GetAttachmentByUUID(ctx context.Context, uuid string) (*Attachment, error)
|
|||
return attach, nil
|
||||
}
|
||||
|
||||
// GetAttachmentsByUUIDs returns attachment by given UUID list.
|
||||
func GetAttachmentsByUUIDs(ctx context.Context, uuids []string) ([]*Attachment, error) {
|
||||
type FindAttachmentOptions struct {
|
||||
ReleaseID int64
|
||||
IssueID int64
|
||||
CommentID int64
|
||||
}
|
||||
|
||||
func (opts FindAttachmentOptions) ToConds() builder.Cond {
|
||||
return builder.Eq{"release_id": opts.ReleaseID, "issue_id": opts.IssueID, "comment_id": opts.CommentID}
|
||||
}
|
||||
|
||||
// FindRepoAttachmentsByUUID always returns attachment that has a UUID that is
|
||||
// in the given `uuids` argument and is attached to the repository.
|
||||
//
|
||||
// The values in `opts` are always as a condition even if they are zero, this
|
||||
// allows to search for attachments that are not yet attached to any resource by
|
||||
// specifying a empty struct.
|
||||
func FindRepoAttachmentsByUUID(ctx context.Context, repoID int64, uuids []string, opts FindAttachmentOptions) ([]*Attachment, error) {
|
||||
// Nothing to match anyway.
|
||||
if len(uuids) == 0 {
|
||||
return []*Attachment{}, nil
|
||||
}
|
||||
|
||||
// Silently drop invalid uuids.
|
||||
// At maximum nothing is filtered and we get all attachments via the UUID.
|
||||
attachments := make([]*Attachment, 0, len(uuids))
|
||||
return attachments, db.GetEngine(ctx).In("uuid", uuids).Find(&attachments)
|
||||
|
||||
err := db.GetEngine(ctx).
|
||||
Where("repo_id = ?", repoID).
|
||||
In("uuid", uuids).
|
||||
And(opts.ToConds()).
|
||||
Find(&attachments)
|
||||
return attachments, err
|
||||
}
|
||||
|
||||
// ExistAttachmentsByUUID returns true if attachment exists with the given UUID
|
||||
|
|
|
|||
|
|
@ -224,18 +224,14 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
|
|||
}
|
||||
|
||||
// AddReleaseAttachments adds a release attachments
|
||||
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
|
||||
// Check attachments
|
||||
attachments, err := GetAttachmentsByUUIDs(ctx, attachmentUUIDs)
|
||||
func AddReleaseAttachments(ctx context.Context, release *Release, attachmentUUIDs []string) (err error) {
|
||||
attachments, err := FindRepoAttachmentsByUUID(ctx, release.RepoID, attachmentUUIDs, FindAttachmentOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetAttachmentsByUUIDs [uuids: %v]: %w", attachmentUUIDs, err)
|
||||
return fmt.Errorf("FindRepoAttachmentsByUUID[uuids=%q,repoID=%d]: %w", attachmentUUIDs, release.RepoID, err)
|
||||
}
|
||||
|
||||
for i := range attachments {
|
||||
if attachments[i].ReleaseID != 0 {
|
||||
return util.NewPermissionDeniedErrorf("release permission denied")
|
||||
}
|
||||
attachments[i].ReleaseID = releaseID
|
||||
attachments[i].ReleaseID = release.ID
|
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue