MM-13718 Prevent files from being attached to multiple posts (#10094)

* MM-13718 Prevent files from being attached to multiple posts

* Switch back to non-batched AttachToPost

* Change status code when failing to attach a file
Этот коммит содержится в:
Harrison Healey
2019-01-22 16:58:22 -04:00
коммит произвёл Christopher Speller
родитель f12680103a
Коммит 6325c5b569
10 изменённых файлов: 176 добавлений и 58 удалений

Просмотреть файл

@@ -201,18 +201,32 @@ func (fs SqlFileInfoStore) GetForUser(userId string) store.StoreChannel {
})
}
func (fs SqlFileInfoStore) AttachToPost(fileId, postId string) store.StoreChannel {
func (fs SqlFileInfoStore) AttachToPost(fileId, postId, creatorId string) store.StoreChannel {
return store.Do(func(result *store.StoreResult) {
if _, err := fs.GetMaster().Exec(
sqlResult, err := fs.GetMaster().Exec(
`UPDATE
FileInfo
SET
PostId = :PostId
WHERE
Id = :Id
AND PostId = ''`, map[string]interface{}{"PostId": postId, "Id": fileId}); err != nil {
AND PostId = ''
AND CreatorId = :CreatorId`, map[string]interface{}{"PostId": postId, "Id": fileId, "CreatorId": creatorId})
if err != nil {
result.Err = model.NewAppError("SqlFileInfoStore.AttachToPost",
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
return
}
count, err := sqlResult.RowsAffected()
if err != nil {
// RowsAffected should never fail with the MySQL or Postgres drivers
result.Err = model.NewAppError("SqlFileInfoStore.AttachToPost",
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId+", err="+err.Error(), http.StatusInternalServerError)
} else if count == 0 {
// Could not attach the file to the post
result.Err = model.NewAppError("SqlFileInfoStore.AttachToPost",
"store.sql_file_info.attach_to_post.app_error", nil, "post_id="+postId+", file_id="+fileId, http.StatusBadRequest)
}
})
}

Просмотреть файл

@@ -456,7 +456,7 @@ type FileInfoStore interface {
GetForPost(postId string, readFromMaster bool, allowFromCache bool) StoreChannel
GetForUser(userId string) StoreChannel
InvalidateFileInfosForPostCache(postId string)
AttachToPost(fileId string, postId string) StoreChannel
AttachToPost(fileId string, postId string, creatorId string) StoreChannel
DeleteForPost(postId string) StoreChannel
PermanentDelete(fileId string) StoreChannel
PermanentDeleteBatch(endTime int64, limit int64) StoreChannel

Просмотреть файл

@@ -9,6 +9,9 @@ import (
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFileInfoStore(t *testing.T, ss store.Store) {
@@ -204,50 +207,71 @@ func testFileInfoGetForUser(t *testing.T, ss store.Store) {
}
func testFileInfoAttachToPost(t *testing.T, ss store.Store) {
userId := model.NewId()
postId := model.NewId()
t.Run("should attach files", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
info1 := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
defer func() {
<-ss.FileInfo().PermanentDelete(info1.Id)
}()
info1 := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
info2 := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
Path: "file2.txt",
})).(*model.FileInfo)
if len(info1.PostId) != 0 {
t.Fatal("file shouldn't have a PostId")
}
require.Equal(t, "", info1.PostId)
require.Equal(t, "", info2.PostId)
if result := <-ss.FileInfo().AttachToPost(info1.Id, postId); result.Err != nil {
t.Fatal(result.Err)
} else {
info1 = store.Must(ss.FileInfo().Get(info1.Id)).(*model.FileInfo)
}
result := <-ss.FileInfo().AttachToPost(info1.Id, postId, userId)
assert.Nil(t, result.Err)
if len(info1.PostId) == 0 {
t.Fatal("file should now have a PostId")
}
result = <-ss.FileInfo().AttachToPost(info2.Id, postId, userId)
assert.Nil(t, result.Err)
info2 := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
defer func() {
<-ss.FileInfo().PermanentDelete(info2.Id)
}()
result = <-ss.FileInfo().GetForPost(postId, true, false)
assert.Nil(t, result.Err)
if result := <-ss.FileInfo().AttachToPost(info2.Id, postId); result.Err != nil {
t.Fatal(result.Err)
} else {
info2 = store.Must(ss.FileInfo().Get(info2.Id)).(*model.FileInfo)
}
data := result.Data.([]*model.FileInfo)
if result := <-ss.FileInfo().GetForPost(postId, true, false); result.Err != nil {
t.Fatal(result.Err)
} else if infos := result.Data.([]*model.FileInfo); len(infos) != 2 {
t.Fatal("should've returned exactly 2 file infos")
}
assert.Len(t, data, 2)
assert.True(t, data[0].Id == info1.Id || data[0].Id == info2.Id)
assert.True(t, data[1].Id == info1.Id || data[1].Id == info2.Id)
})
t.Run("should not attach files to multiple posts", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
info := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: userId,
Path: "file.txt",
})).(*model.FileInfo)
require.Equal(t, "", info.PostId)
result := <-ss.FileInfo().AttachToPost(info.Id, model.NewId(), userId)
assert.Nil(t, result.Err)
result = <-ss.FileInfo().AttachToPost(info.Id, postId, userId)
assert.NotNil(t, result.Err)
})
t.Run("should not attach files owned from a different user", func(t *testing.T) {
userId := model.NewId()
postId := model.NewId()
info := store.Must(ss.FileInfo().Save(&model.FileInfo{
CreatorId: model.NewId(),
Path: "file.txt",
})).(*model.FileInfo)
require.Equal(t, "", info.PostId)
result := <-ss.FileInfo().AttachToPost(info.Id, postId, userId)
assert.NotNil(t, result.Err)
})
}
func testFileInfoDeleteForPost(t *testing.T, ss store.Store) {

Просмотреть файл

@@ -13,13 +13,13 @@ type FileInfoStore struct {
mock.Mock
}
// AttachToPost provides a mock function with given fields: fileId, postId
func (_m *FileInfoStore) AttachToPost(fileId string, postId string) store.StoreChannel {
ret := _m.Called(fileId, postId)
// AttachToPost provides a mock function with given fields: fileId, postId, creatorId
func (_m *FileInfoStore) AttachToPost(fileId string, postId string, creatorId string) store.StoreChannel {
ret := _m.Called(fileId, postId, creatorId)
var r0 store.StoreChannel
if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
r0 = rf(fileId, postId)
if rf, ok := ret.Get(0).(func(string, string, string) store.StoreChannel); ok {
r0 = rf(fileId, postId, creatorId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.StoreChannel)