mattermost/server/channels/app/file_test.go
Rajat Dabade 0481bd1fb0
Fliter post in search api with no read content channel permission (#34620)
* Fliter post in search api with no read content channel permission

* Added test

* Review comments

* reverted the unnecessary code

* linter fixes

* Fix filter functions to handle non-existent channels gracefully

When filtering posts/files by channel permissions, GetChannels() returns
a 404 error if all requested channels don't exist. This caused the
entire filter operation to fail. Now we ignore 404 errors and continue
processing, allowing non-existent channels to be filtered out as expected.

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Jesse Hallam <jesse@mattermost.com>
2026-01-07 10:21:55 -04:00

976 lines
31 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"archive/zip"
"bytes"
"errors"
"fmt"
"image"
"io"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
storemocks "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
eMocks "github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine/mocks"
)
func TestGeneratePublicLinkHash(t *testing.T) {
mainHelper.Parallel(t)
filename1 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
filename2 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
salt1 := model.NewRandomString(32)
salt2 := model.NewRandomString(32)
hash1 := GeneratePublicLinkHash(filename1, salt1)
hash2 := GeneratePublicLinkHash(filename2, salt1)
hash3 := GeneratePublicLinkHash(filename1, salt2)
hash := GeneratePublicLinkHash(filename1, salt1)
assert.Equal(t, hash, hash1, "hash should be equal for the same file name and salt")
assert.NotEqual(t, hash1, hash2, "hashes for different files should not be equal")
assert.NotEqual(t, hash1, hash3, "hashes for the same file with different salts should not be equal")
}
func TestDoUploadFile(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
teamID := model.NewId()
channelID := model.NewId()
userID := model.NewId()
filename := "test"
data := []byte("abcd")
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
require.Nil(t, err, "DoUploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info1.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info1.Path)
require.Nil(t, appErr)
}()
value := fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamID, channelID, userID, info1.Id, filename)
assert.Equal(t, value, info1.Path, "stored file at incorrect path")
info2, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
require.Nil(t, err, "DoUploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info2.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info2.Path)
require.Nil(t, appErr)
}()
value = fmt.Sprintf("20070204/teams/%v/channels/%v/users/%v/%v/%v", teamID, channelID, userID, info2.Id, filename)
assert.Equal(t, value, info2.Path, "stored file at incorrect path")
info3, err := th.App.DoUploadFile(th.Context, time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
require.Nil(t, err, "DoUploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info3.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info3.Path)
require.Nil(t, appErr)
}()
value = fmt.Sprintf("20080305/teams/%v/channels/%v/users/%v/%v/%v", teamID, channelID, userID, info3.Id, filename)
assert.Equal(t, value, info3.Path, "stored file at incorrect path")
info4, err := th.App.DoUploadFile(th.Context, time.Date(2009, 3, 5, 1, 2, 3, 4, time.Local), "../../"+teamID, "../../"+channelID, "../../"+userID, "../../"+filename, data, true)
require.Nil(t, err, "DoUploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info4.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info4.Path)
require.Nil(t, appErr)
}()
value = fmt.Sprintf("20090305/teams/%v/channels/%v/users/%v/%v/%v", teamID, channelID, userID, info4.Id, filename)
assert.Equal(t, value, info4.Path, "stored file at incorrect path")
info5, err := th.App.DoUploadFile(th.Context, time.Date(2008, 3, 5, 1, 2, 3, 4, time.Local), teamID, channelID, model.BookmarkFileOwner, filename, data, true)
require.Nil(t, err, "DoUploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info5.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info5.Path)
require.Nil(t, appErr)
}()
value = fmt.Sprintf("%v/teams/%v/channels/%v/%v/%v", model.BookmarkFileOwner, teamID, channelID, info5.Id, filename)
assert.Equal(t, value, info5.Path, "stored file at incorrect path")
}
func TestUploadFile(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
channelID := th.BasicChannel.Id
filename := "test"
data := []byte("abcd")
info1, err := th.App.UploadFile(th.Context, data, "wrong", filename)
require.NotNil(t, err, "Wrong Channel ID.")
require.Nil(t, info1, "Channel ID does not exist.")
info1, err = th.App.UploadFile(th.Context, data, "", filename)
require.Nil(t, err, "empty channel IDs should be valid")
require.NotNil(t, info1)
info1, err = th.App.UploadFile(th.Context, data, channelID, filename)
require.Nil(t, err, "UploadFile should succeed with valid data")
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info1.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info1.Path)
require.Nil(t, appErr)
}()
value := fmt.Sprintf("%v/teams/noteam/channels/%v/users/nouser/%v/%v",
time.Now().Format("20060102"), channelID, info1.Id, filename)
assert.Equal(t, value, info1.Path, "Stored file at incorrect path")
}
func TestParseOldFilenames(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
fileID := model.NewId()
tests := []struct {
description string
filenames []string
channelID string
userID string
expected [][]string
}{
{
description: "Empty input should result in empty output",
filenames: []string{},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{},
},
{
description: "Filename with invalid format should not parse",
filenames: []string{"/path/to/some/file.png"},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{},
},
{
description: "ChannelId in Filename should not match",
filenames: []string{
fmt.Sprintf("/%v/%v/%v/file.png", model.NewId(), th.BasicUser.Id, fileID),
},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{},
},
{
description: "UserId in Filename should not match",
filenames: []string{
fmt.Sprintf("/%v/%v/%v/file.png", th.BasicChannel.Id, model.NewId(), fileID),
},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{},
},
{
description: "../ in filename should not parse",
filenames: []string{
fmt.Sprintf("/%v/%v/%v/../../../file.png", th.BasicChannel.Id, th.BasicUser.Id, fileID),
},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{},
},
{
description: "Should only parse valid filenames",
filenames: []string{
fmt.Sprintf("/%v/%v/%v/../otherfile.png", th.BasicChannel.Id, th.BasicUser.Id, fileID),
fmt.Sprintf("/%v/%v/%v/file.png", th.BasicChannel.Id, th.BasicUser.Id, fileID),
},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{
{
th.BasicChannel.Id,
th.BasicUser.Id,
fileID,
"file.png",
},
},
},
{
description: "Valid Filename should parse",
filenames: []string{
fmt.Sprintf("/%v/%v/%v/file.png", th.BasicChannel.Id, th.BasicUser.Id, fileID),
},
channelID: th.BasicChannel.Id,
userID: th.BasicUser.Id,
expected: [][]string{
{
th.BasicChannel.Id,
th.BasicUser.Id,
fileID,
"file.png",
},
},
},
}
for _, test := range tests {
t.Run(test.description, func(tt *testing.T) {
result := parseOldFilenames(th.Context, test.filenames, test.channelID, test.userID)
require.Equal(tt, result, test.expected)
})
}
}
func TestGetInfoForFilename(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
post := th.BasicPost
teamID := th.BasicTeam.Id
info := th.App.getInfoForFilename(th.Context, post, teamID, post.ChannelId, post.UserId, "someid", "somefile.png")
assert.Nil(t, info, "Test non-existent file")
}
func TestFindTeamIdForFilename(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
teamID := th.App.findTeamIdForFilename(th.Context, th.BasicPost, "someid", "somefile.png")
assert.Equal(t, th.BasicTeam.Id, teamID)
_, err := th.App.CreateTeamWithUser(th.Context, &model.Team{Email: th.BasicUser.Email, Name: "zz" + model.NewId(), DisplayName: "Joram's Test Team", Type: model.TeamOpen}, th.BasicUser.Id)
require.Nil(t, err)
teamID = th.App.findTeamIdForFilename(th.Context, th.BasicPost, "someid", "somefile.png")
assert.Equal(t, "", teamID)
}
func TestMigrateFilenamesToFileInfos(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
post := th.BasicPost
infos := th.App.MigrateFilenamesToFileInfos(th.Context, post)
assert.Equal(t, 0, len(infos))
post.Filenames = []string{fmt.Sprintf("/%v/%v/%v/blargh.png", th.BasicChannel.Id, th.BasicUser.Id, "someid")}
infos = th.App.MigrateFilenamesToFileInfos(th.Context, post)
assert.Equal(t, 0, len(infos))
path, _ := fileutils.FindDir("tests")
file, fileErr := os.Open(filepath.Join(path, "test.png"))
require.NoError(t, fileErr)
defer file.Close()
fileID := model.NewId()
fpath := fmt.Sprintf("/teams/%v/channels/%v/users/%v/%v/test.png", th.BasicTeam.Id, th.BasicChannel.Id, th.BasicUser.Id, fileID)
_, err := th.App.WriteFile(file, fpath)
require.Nil(t, err)
rpost, err := th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, err)
infos = th.App.MigrateFilenamesToFileInfos(th.Context, rpost)
assert.Equal(t, 1, len(infos))
rpost, err = th.App.CreatePost(th.Context, &model.Post{UserId: th.BasicUser.Id, ChannelId: th.BasicChannel.Id, Filenames: []string{fmt.Sprintf("/%v/%v/%v/../../test.png", th.BasicChannel.Id, th.BasicUser.Id, fileID)}}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
require.Nil(t, err)
infos = th.App.MigrateFilenamesToFileInfos(th.Context, rpost)
assert.Equal(t, 0, len(infos))
}
func TestWriteZipFile(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
t.Run("write no file", func(t *testing.T) {
buf := new(bytes.Buffer)
err := th.App.WriteZipFile(buf, []model.FileData{})
require.NoError(t, err)
// Verify it's a valid zip file
reader := bytes.NewReader(buf.Bytes())
z, err := zip.NewReader(reader, int64(buf.Len()))
require.NoError(t, err)
require.Len(t, z.File, 0)
})
t.Run("write one file", func(t *testing.T) {
buf := new(bytes.Buffer)
err := th.App.WriteZipFile(buf, []model.FileData{
{
Filename: "file1.txt",
Body: []byte("content1"),
},
})
require.NoError(t, err)
// Verify the zip contents
reader := bytes.NewReader(buf.Bytes())
z, err := zip.NewReader(reader, int64(buf.Len()))
require.NoError(t, err)
require.Len(t, z.File, 1)
file := z.File[0]
assert.Equal(t, "file1.txt", file.Name)
now := time.Now().Truncate(time.Second) // Files are stored with a second precision
// Confirm that the file was created in the last 10 seconds
assert.GreaterOrEqual(t, file.Modified, now.Add(-10*time.Second))
assert.GreaterOrEqual(t, now, file.Modified)
// Check file content
fr, err := file.Open()
require.NoError(t, err)
t.Cleanup(func() {
err = fr.Close()
require.NoError(t, err)
})
content, err := io.ReadAll(fr)
require.NoError(t, err)
assert.Equal(t, []byte("content1"), content)
})
t.Run("write multiple files", func(t *testing.T) {
buf := new(bytes.Buffer)
fileDatas := []model.FileData{
{
Filename: "file1.txt",
Body: []byte("content1"),
},
{
Filename: "file2.txt",
Body: []byte("content2"),
},
{
Filename: "dir/file3.txt",
Body: []byte("content3"),
},
}
err := th.App.WriteZipFile(buf, fileDatas)
require.NoError(t, err)
// Verify the zip contents
reader := bytes.NewReader(buf.Bytes())
z, err := zip.NewReader(reader, int64(buf.Len()))
require.NoError(t, err)
require.Len(t, z.File, 3)
// Check each file
for i, zf := range z.File {
assert.Equal(t, fileDatas[i].Filename, zf.Name)
fr, err := zf.Open()
require.NoError(t, err)
content, err := io.ReadAll(fr)
require.NoError(t, err)
assert.Equal(t, fileDatas[i].Body, content)
err = fr.Close()
require.NoError(t, err)
}
})
}
func TestCopyFileInfos(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
teamID := model.NewId()
channelID := model.NewId()
userID := model.NewId()
filename := "test"
data := []byte("abcd")
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
require.Nil(t, err)
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info1.Id)
require.NoError(t, err)
}()
infoIds, err := th.App.CopyFileInfos(th.Context, userID, []string{info1.Id})
require.Nil(t, err)
info2, err := th.App.GetFileInfo(th.Context, infoIds[0])
require.Nil(t, err)
defer func() {
err := th.App.Srv().Store().FileInfo().PermanentDelete(th.Context, info2.Id)
require.NoError(t, err)
appErr := th.App.RemoveFile(info2.Path)
require.Nil(t, appErr)
}()
assert.NotEqual(t, info1.Id, info2.Id, "should not be equal")
assert.Equal(t, info2.PostId, "", "should be empty string")
}
func TestGenerateThumbnailImage(t *testing.T) {
mainHelper.Parallel(t)
t.Run("test generating thumbnail image", func(t *testing.T) {
// given
th := Setup(t)
img := createDummyImage()
dataPath := *th.App.Config().FileSettings.Directory
thumbnailName := "thumb.jpg"
thumbnailPath := filepath.Join(dataPath, thumbnailName)
// when
th.App.generateThumbnailImage(th.Context, img, "jpg", thumbnailName)
defer os.Remove(thumbnailPath)
// then
outputImage, err := os.Stat(thumbnailPath)
assert.NoError(t, err)
assert.Equal(t, int64(721), outputImage.Size())
})
}
func createDummyImage() *image.RGBA {
width := 200
height := 100
upperLeftCorner := image.Point{0, 0}
lowerRightCorner := image.Point{width, height}
return image.NewRGBA(image.Rectangle{upperLeftCorner, lowerRightCorner})
}
func TestSearchFilesInTeamForUser(t *testing.T) {
mainHelper.Parallel(t)
perPage := 5
searchTerm := "searchTerm"
setup := func(t *testing.T, enableElasticsearch bool) (*TestHelper, []*model.FileInfo) {
th := Setup(t).InitBasic(t)
fileInfos := make([]*model.FileInfo, 7)
for i := 0; i < cap(fileInfos); i++ {
fileInfo, err := th.App.Srv().Store().FileInfo().Save(th.Context,
&model.FileInfo{
CreatorId: th.BasicUser.Id,
PostId: th.BasicPost.Id,
ChannelId: th.BasicPost.ChannelId,
Name: searchTerm,
Path: searchTerm,
Extension: "jpg",
MimeType: "image/jpeg",
})
time.Sleep(1 * time.Millisecond)
require.NoError(t, err)
fileInfos[i] = fileInfo
}
if enableElasticsearch {
th.App.Srv().SetLicense(model.NewTestLicense("elastic_search"))
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ElasticsearchSettings.EnableIndexing = true
*cfg.ElasticsearchSettings.EnableSearching = true
})
} else {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ElasticsearchSettings.EnableSearching = false
})
}
return th, fileInfos
}
t.Run("should return everything as first page of fileInfos from database", func(t *testing.T) {
th, fileInfos := setup(t, false)
page := 0
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
require.NotNil(t, results)
assert.Equal(t, []string{
fileInfos[6].Id,
fileInfos[5].Id,
fileInfos[4].Id,
fileInfos[3].Id,
fileInfos[2].Id,
fileInfos[1].Id,
fileInfos[0].Id,
}, results.Order)
})
t.Run("should not return later pages of fileInfos from database", func(t *testing.T) {
th, _ := setup(t, false)
page := 1
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
require.NotNil(t, results)
assert.Equal(t, []string{}, results.Order)
})
t.Run("should return first page of fileInfos from ElasticSearch", func(t *testing.T) {
th, fileInfos := setup(t, true)
page := 0
resultsPage := []string{
fileInfos[6].Id,
fileInfos[5].Id,
fileInfos[4].Id,
fileInfos[3].Id,
fileInfos[2].Id,
}
es := &mocks.SearchEngineInterface{}
es.On("SearchFiles", mock.Anything, mock.Anything, page, perPage).Return(resultsPage, nil)
es.On("Start").Return(nil).Maybe()
es.On("IsActive").Return(true)
es.On("IsSearchEnabled").Return(true)
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
defer func() {
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
}()
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
require.NotNil(t, results)
assert.Equal(t, resultsPage, results.Order)
es.AssertExpectations(t)
})
t.Run("should return later pages of fileInfos from ElasticSearch", func(t *testing.T) {
th, fileInfos := setup(t, true)
page := 1
resultsPage := []string{
fileInfos[1].Id,
fileInfos[0].Id,
}
es := &mocks.SearchEngineInterface{}
es.On("SearchFiles", mock.Anything, mock.Anything, page, perPage).Return(resultsPage, nil)
es.On("Start").Return(nil).Maybe()
es.On("IsActive").Return(true)
es.On("IsSearchEnabled").Return(true)
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
defer func() {
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
}()
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
require.NotNil(t, results)
assert.Equal(t, resultsPage, results.Order)
es.AssertExpectations(t)
})
t.Run("should fall back to database if ElasticSearch fails on first page", func(t *testing.T) {
th, fileInfos := setup(t, true)
page := 0
es := &mocks.SearchEngineInterface{}
es.On("SearchFiles", mock.Anything, mock.Anything, page, perPage).Return(nil, &model.AppError{})
es.On("GetName").Return("mock")
es.On("Start").Return(nil).Maybe()
es.On("IsActive").Return(true)
es.On("IsSearchEnabled").Return(true)
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
defer func() {
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
}()
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
require.NotNil(t, results)
assert.Equal(t, []string{
fileInfos[6].Id,
fileInfos[5].Id,
fileInfos[4].Id,
fileInfos[3].Id,
fileInfos[2].Id,
fileInfos[1].Id,
fileInfos[0].Id,
}, results.Order)
es.AssertExpectations(t)
})
t.Run("should return nothing if ElasticSearch fails on later pages", func(t *testing.T) {
th, _ := setup(t, true)
page := 1
es := &mocks.SearchEngineInterface{}
es.On("SearchFiles", mock.Anything, mock.Anything, page, perPage).Return(nil, &model.AppError{})
es.On("GetName").Return("mock")
es.On("Start").Return(nil).Maybe()
es.On("IsActive").Return(true)
es.On("IsSearchEnabled").Return(true)
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = es
defer func() {
th.App.Srv().Platform().SearchEngine.ElasticsearchEngine = nil
}()
results, err := th.App.SearchFilesInTeamForUser(th.Context, searchTerm, th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, page, perPage)
require.Nil(t, err)
assert.Equal(t, []string{}, results.Order)
es.AssertExpectations(t)
})
}
func TestExtractContentFromFileInfo(t *testing.T) {
mainHelper.Parallel(t)
app := &App{}
fi := &model.FileInfo{
MimeType: "image/jpeg",
}
// Test that we don't process images.
require.NoError(t, app.ExtractContentFromFileInfo(request.TestContext(t), fi))
}
func TestGetLastAccessibleFileTime(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
r, err := th.App.GetLastAccessibleFileTime()
require.Nil(t, err)
assert.Equal(t, int64(0), r)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockSystemStore := storemocks.SystemStore{}
mockStore.On("System").Return(&mockSystemStore)
mockSystemStore.On("GetByName", mock.Anything).Return(nil, store.NewErrNotFound("", ""))
r, err = th.App.GetLastAccessibleFileTime()
require.Nil(t, err)
assert.Equal(t, int64(0), r)
mockSystemStore = storemocks.SystemStore{}
mockStore.On("System").Return(&mockSystemStore)
mockSystemStore.On("GetByName", mock.Anything).Return(nil, errors.New("test"))
_, err = th.App.GetLastAccessibleFileTime()
require.NotNil(t, err)
mockSystemStore = storemocks.SystemStore{}
mockStore.On("System").Return(&mockSystemStore)
mockSystemStore.On("GetByName", mock.Anything).Return(&model.System{Name: model.SystemLastAccessibleFileTime, Value: "10"}, nil)
r, err = th.App.GetLastAccessibleFileTime()
require.Nil(t, err)
assert.Equal(t, int64(10), r)
}
func TestComputeLastAccessibleFileTime(t *testing.T) {
mainHelper.Parallel(t)
t.Run("Updates the time, if cloud limit is applicable", func(t *testing.T) {
th := SetupWithStoreMock(t)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := &eMocks.CloudInterface{}
th.App.Srv().Cloud = cloud
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(&model.ProductLimits{
Files: &model.FilesLimits{
TotalStorage: model.NewPointer(int64(1)),
},
}, nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockFileStore := storemocks.FileInfoStore{}
mockFileStore.On("GetUptoNSizeFileTime", mock.Anything).Return(int64(1), nil)
mockSystemStore := storemocks.SystemStore{}
mockSystemStore.On("SaveOrUpdate", mock.Anything).Return(nil)
mockStore.On("FileInfo").Return(&mockFileStore)
mockStore.On("System").Return(&mockSystemStore)
err := th.App.ComputeLastAccessibleFileTime()
require.NoError(t, err)
mockSystemStore.AssertCalled(t, "SaveOrUpdate", mock.Anything)
})
t.Run("Removes the time, if cloud limit is not applicable", func(t *testing.T) {
th := SetupWithStoreMock(t)
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
cloud := &eMocks.CloudInterface{}
th.App.Srv().Cloud = cloud
cloud.Mock.On("GetCloudLimits", mock.Anything).Return(nil, nil)
mockStore := th.App.Srv().Store().(*storemocks.Store)
mockFileStore := storemocks.FileInfoStore{}
mockFileStore.On("GetUptoNSizeFileTime", mock.Anything).Return(int64(1), nil)
mockSystemStore := storemocks.SystemStore{}
mockSystemStore.On("GetByName", mock.Anything).Return(&model.System{Name: model.SystemLastAccessibleFileTime, Value: "10"}, nil)
mockSystemStore.On("PermanentDeleteByName", mock.Anything).Return(nil, nil)
mockSystemStore.On("SaveOrUpdate", mock.Anything).Return(nil)
mockStore.On("FileInfo").Return(&mockFileStore)
mockStore.On("System").Return(&mockSystemStore)
err := th.App.ComputeLastAccessibleFileTime()
require.NoError(t, err)
mockSystemStore.AssertNotCalled(t, "SaveOrUpdate", mock.Anything)
mockSystemStore.AssertCalled(t, "PermanentDeleteByName", mock.Anything)
})
}
func TestSetFileSearchableContent(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
fileInfo, err := th.App.Srv().Store().FileInfo().Save(th.Context,
&model.FileInfo{
CreatorId: th.BasicUser.Id,
PostId: th.BasicPost.Id,
ChannelId: th.BasicPost.ChannelId,
Name: "test",
Path: "test",
Extension: "jpg",
MimeType: "image/jpeg",
})
require.NoError(t, err)
result, appErr := th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60)
require.Nil(t, appErr)
assert.Equal(t, 0, len(result.Order))
appErr = th.App.SetFileSearchableContent(th.Context, fileInfo.Id, "searchable")
require.Nil(t, appErr)
result, appErr = th.App.SearchFilesInTeamForUser(th.Context, "searchable", th.BasicUser.Id, th.BasicTeam.Id, false, false, 0, 0, 60)
require.Nil(t, appErr)
assert.Equal(t, 1, len(result.Order))
}
func TestPermanentDeleteFilesByPost(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
t.Run("should delete files for post", func(t *testing.T) {
// Create a post with a file attachment.
teamID := th.BasicTeam.Id
channelID := th.BasicChannel.Id
userID := th.BasicUser.Id
filename := "test"
data := []byte("abcd")
info1, err := th.App.DoUploadFile(th.Context, time.Date(2007, 2, 4, 1, 2, 3, 4, time.Local), teamID, channelID, userID, filename, data, true)
require.Nil(t, err)
post := &model.Post{
Message: "asd",
ChannelId: channelID,
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: userID,
CreateAt: 0,
FileIds: []string{info1.Id},
}
post, err = th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
assert.Nil(t, err)
err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id)
require.Nil(t, err)
_, err = th.App.GetFileInfo(th.Context, info1.Id)
require.NotNil(t, err)
})
t.Run("should not delete files for post that doesn't exist", func(t *testing.T) {
err := th.App.PermanentDeleteFilesByPost(th.Context, "postId1")
assert.Nil(t, err)
})
t.Run("should handle empty file list", func(t *testing.T) {
post := &model.Post{
Message: "asd",
ChannelId: th.BasicChannel.Id,
PendingPostId: model.NewId() + ":" + fmt.Sprint(model.GetMillis()),
UserId: th.BasicUser.Id,
CreateAt: 0,
}
post, err := th.App.CreatePost(th.Context, post, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
assert.Nil(t, err)
err = th.App.PermanentDeleteFilesByPost(th.Context, post.Id)
assert.Nil(t, err)
})
}
func TestFilterFilesByChannelPermissions(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.GuestAccountsSettings.Enable = true
})
guestUser := th.CreateGuest(t)
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guestUser.Id, "")
require.Nil(t, appErr)
privateChannel := th.CreatePrivateChannel(t, th.BasicTeam)
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, privateChannel, false)
require.Nil(t, appErr)
_, appErr = th.App.AddUserToChannel(th.Context, guestUser, th.BasicChannel, false)
require.Nil(t, appErr)
post1 := th.CreatePost(t, th.BasicChannel)
post2 := th.CreatePost(t, privateChannel)
post3 := th.CreatePost(t, th.BasicChannel)
fileInfo1 := th.CreateFileInfo(t, th.BasicUser.Id, post1.Id, th.BasicChannel.Id)
fileInfo2 := th.CreateFileInfo(t, th.BasicUser.Id, post2.Id, privateChannel.Id)
fileInfo3 := th.CreateFileInfo(t, th.BasicUser.Id, post3.Id, th.BasicChannel.Id)
t.Run("should filter files when user has read_channel_content permission", func(t *testing.T) {
fileList := model.NewFileInfoList()
fileList.FileInfos[fileInfo1.Id] = fileInfo1
fileList.FileInfos[fileInfo2.Id] = fileInfo2
fileList.FileInfos[fileInfo3.Id] = fileInfo3
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
// BasicUser should have access to all files
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 3)
require.Len(t, fileList.Order, 3)
})
t.Run("should filter files when guest has read_channel_content permission", func(t *testing.T) {
fileList := model.NewFileInfoList()
fileList.FileInfos[fileInfo1.Id] = fileInfo1
fileList.FileInfos[fileInfo2.Id] = fileInfo2
fileList.FileInfos[fileInfo3.Id] = fileInfo3
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 3)
require.Len(t, fileList.Order, 3)
})
t.Run("should filter files when guest does not have read_channel_content permission", func(t *testing.T) {
channelGuestRole, appErr := th.App.GetRoleByName(th.Context, model.ChannelGuestRoleId)
require.Nil(t, appErr)
originalPermissions := make([]string, len(channelGuestRole.Permissions))
copy(originalPermissions, channelGuestRole.Permissions)
newPermissions := []string{}
for _, perm := range channelGuestRole.Permissions {
if perm != model.PermissionReadChannelContent.Id && perm != model.PermissionReadChannel.Id {
newPermissions = append(newPermissions, perm)
}
}
_, appErr = th.App.PatchRole(channelGuestRole, &model.RolePatch{
Permissions: &newPermissions,
})
require.Nil(t, appErr)
defer func() {
_, err := th.App.PatchRole(channelGuestRole, &model.RolePatch{
Permissions: &originalPermissions,
})
require.Nil(t, err)
}()
fileList := model.NewFileInfoList()
fileList.FileInfos[fileInfo1.Id] = fileInfo1
fileList.FileInfos[fileInfo2.Id] = fileInfo2
fileList.FileInfos[fileInfo3.Id] = fileInfo3
fileList.Order = []string{fileInfo1.Id, fileInfo2.Id, fileInfo3.Id}
appErr = th.App.FilterFilesByChannelPermissions(th.Context, fileList, guestUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 0)
require.Len(t, fileList.Order, 0)
})
t.Run("should handle empty file list", func(t *testing.T) {
fileList := model.NewFileInfoList()
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 0)
require.Len(t, fileList.Order, 0)
})
t.Run("should handle nil file list", func(t *testing.T) {
appErr := th.App.FilterFilesByChannelPermissions(th.Context, nil, th.BasicUser.Id)
require.Nil(t, appErr)
})
t.Run("should handle files with empty channel IDs", func(t *testing.T) {
fileList := model.NewFileInfoList()
fileWithoutChannel := &model.FileInfo{
Id: model.NewId(),
ChannelId: "",
Name: "test.txt",
}
fileList.FileInfos[fileWithoutChannel.Id] = fileWithoutChannel
fileList.Order = []string{fileWithoutChannel.Id}
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 0)
require.Len(t, fileList.Order, 0)
})
t.Run("should handle files from non-existent channels", func(t *testing.T) {
fileList := model.NewFileInfoList()
fileWithInvalidChannel := &model.FileInfo{
Id: model.NewId(),
ChannelId: model.NewId(),
Name: "test.txt",
}
fileList.FileInfos[fileWithInvalidChannel.Id] = fileWithInvalidChannel
fileList.Order = []string{fileWithInvalidChannel.Id}
appErr := th.App.FilterFilesByChannelPermissions(th.Context, fileList, th.BasicUser.Id)
require.Nil(t, appErr)
require.Len(t, fileList.FileInfos, 0)
require.Len(t, fileList.Order, 0)
})
}