mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-03-25 13:43:03 -04:00
The permission check for editing the attachments of a comment (adding or removing them) is changed to be the same as when editing the textual body of the comment. The poster of a comment can always edit it via the web UI, which includes the ability to remove or add attachments. It does not require write permission on the issue or pull unit of the repository. The API is consistent with the web UI in how it [verifies permissions for editing comments][0] when modifying the textual content. However, when adding or removing the attachments of a comment, it [also requires write permissions][1] on the issue or pull unit, which is inconsistent with the web UI and more restrictive. [0]:a581059606/routers/api/v1/repo/issue_comment.go (L606)[1]:a581059606/routers/api/v1/repo/issue_comment_attachment.go (L359)Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11623 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: limiting-factor <limiting-factor@posteo.com> Co-committed-by: limiting-factor <limiting-factor@posteo.com>
261 lines
11 KiB
Go
261 lines
11 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
auth_model "forgejo.org/models/auth"
|
|
"forgejo.org/models/db"
|
|
issues_model "forgejo.org/models/issues"
|
|
repo_model "forgejo.org/models/repo"
|
|
"forgejo.org/models/unittest"
|
|
user_model "forgejo.org/models/user"
|
|
api "forgejo.org/modules/structs"
|
|
"forgejo.org/services/convert"
|
|
"forgejo.org/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAPIGetCommentAttachment(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
|
require.NoError(t, comment.LoadIssue(db.DefaultContext))
|
|
require.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
|
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID})
|
|
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", repoOwner.Name, repo.Name, comment.ID, attachment.ID).
|
|
AddTokenAuth(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", repoOwner.Name, repo.Name, comment.ID).
|
|
AddTokenAuth(token)
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
var apiComment api.Comment
|
|
DecodeJSON(t, resp, &apiComment)
|
|
assert.NotEmpty(t, apiComment.Attachments)
|
|
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusOK)
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID).
|
|
AddTokenAuth(token)
|
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
var apiAttachment api.Attachment
|
|
DecodeJSON(t, resp, &apiAttachment)
|
|
|
|
expect := convert.ToAPIAttachment(repo, attachment)
|
|
assert.Equal(t, expect.ID, apiAttachment.ID)
|
|
assert.Equal(t, expect.Name, apiAttachment.Name)
|
|
assert.Equal(t, expect.UUID, apiAttachment.UUID)
|
|
assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix())
|
|
assert.Equal(t, expect.DownloadURL, apiAttachment.DownloadURL)
|
|
}
|
|
|
|
func TestAPIListCommentAttachments(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
|
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})
|
|
|
|
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", repoOwner.Name, repo.Name, comment.ID).
|
|
AddTokenAuth(token)
|
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
|
|
var apiAttachments []*api.Attachment
|
|
DecodeJSON(t, resp, &apiAttachments)
|
|
expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID})
|
|
assert.Len(t, apiAttachments, expectedCount)
|
|
|
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID})
|
|
}
|
|
|
|
func TestAPICreateCommentAttachment(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 3})
|
|
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})
|
|
commentPoster := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: comment.PosterID})
|
|
|
|
session := loginUser(t, commentPoster.Name)
|
|
require.NotEqual(t, commentPoster.Name, repoOwner.Name)
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
filename := "image.png"
|
|
buff := generateImg()
|
|
body := &bytes.Buffer{}
|
|
|
|
// Setup multi-part
|
|
contentType := tests.WriteImageBody(t, buff, filename, body)
|
|
|
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body).
|
|
AddTokenAuth(token).
|
|
SetHeader("Content-Type", contentType)
|
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
|
|
|
apiAttachment := new(api.Attachment)
|
|
DecodeJSON(t, resp, &apiAttachment)
|
|
|
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
|
|
}
|
|
|
|
func TestAPICreateCommentAttachmentAutoDate(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
|
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})
|
|
|
|
session := loginUser(t, repoOwner.Name)
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets",
|
|
repoOwner.Name, repo.Name, comment.ID)
|
|
|
|
filename := "image.png"
|
|
buff := generateImg()
|
|
body := &bytes.Buffer{}
|
|
|
|
t.Run("WithAutoDate", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
// Setup multi-part
|
|
contentType := tests.WriteImageBody(t, buff, filename, body)
|
|
|
|
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
|
req.Header.Add("Content-Type", contentType)
|
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
|
apiAttachment := new(api.Attachment)
|
|
DecodeJSON(t, resp, &apiAttachment)
|
|
|
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID})
|
|
// the execution of the API call supposedly lasted less than one minute
|
|
updatedSince := time.Since(apiAttachment.Created)
|
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
|
|
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID})
|
|
updatedSince = time.Since(commentAfter.UpdatedUnix.AsTime())
|
|
assert.LessOrEqual(t, updatedSince, time.Minute)
|
|
})
|
|
|
|
t.Run("WithUpdateDate", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
updatedAt := time.Now().Add(-time.Hour).Truncate(time.Second)
|
|
urlStr += fmt.Sprintf("?updated_at=%s", updatedAt.UTC().Format(time.RFC3339))
|
|
|
|
// Setup multi-part
|
|
contentType := tests.WriteImageBody(t, buff, filename, body)
|
|
|
|
req := NewRequestWithBody(t, "POST", urlStr, body).AddTokenAuth(token)
|
|
req.Header.Add("Content-Type", contentType)
|
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
|
apiAttachment := new(api.Attachment)
|
|
DecodeJSON(t, resp, &apiAttachment)
|
|
|
|
// dates will be converted into the same tz, in order to compare them
|
|
utcTZ, _ := time.LoadLocation("UTC")
|
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID})
|
|
assert.Equal(t, updatedAt.In(utcTZ), apiAttachment.Created.In(utcTZ))
|
|
|
|
commentAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID})
|
|
assert.Equal(t, updatedAt.In(utcTZ), commentAfter.UpdatedUnix.AsTime().In(utcTZ))
|
|
})
|
|
}
|
|
|
|
func TestAPIEditCommentAttachment(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
const newAttachmentName = "newAttachmentName"
|
|
|
|
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6})
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID})
|
|
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})
|
|
|
|
session := loginUser(t, repoOwner.Name)
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d",
|
|
repoOwner.Name, repo.Name, comment.ID, attachment.ID)
|
|
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
|
|
"name": newAttachmentName,
|
|
}).AddTokenAuth(token)
|
|
resp := session.MakeRequest(t, req, http.StatusCreated)
|
|
apiAttachment := new(api.Attachment)
|
|
DecodeJSON(t, resp, &apiAttachment)
|
|
|
|
unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name})
|
|
}
|
|
|
|
func TestAPIDeleteCommentAttachment(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6})
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID})
|
|
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})
|
|
|
|
session := loginUser(t, repoOwner.Name)
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID)).
|
|
AddTokenAuth(token)
|
|
session.MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: attachment.ID, CommentID: comment.ID})
|
|
}
|
|
|
|
func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
|
|
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})
|
|
|
|
session := loginUser(t, repoOwner.Name)
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
filename := "file.bad"
|
|
body := &bytes.Buffer{}
|
|
|
|
// Setup multi-part.
|
|
writer := multipart.NewWriter(body)
|
|
_, err := writer.CreateFormFile("attachment", filename)
|
|
require.NoError(t, err)
|
|
err = writer.Close()
|
|
require.NoError(t, err)
|
|
|
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body).
|
|
AddTokenAuth(token).
|
|
SetHeader("Content-Type", writer.FormDataContentType())
|
|
|
|
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
}
|