diff --git a/server/channels/api4/user_test.go b/server/channels/api4/user_test.go index d64e8dd2a4..de4e7c5f08 100644 --- a/server/channels/api4/user_test.go +++ b/server/channels/api4/user_test.go @@ -26,7 +26,7 @@ import ( "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mail" - _ "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab" + _ "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab" ) func TestCreateUser(t *testing.T) { diff --git a/server/channels/app/emoji.go b/server/channels/app/emoji.go index 5a7a847d0a..02725afe01 100644 --- a/server/channels/app/emoji.go +++ b/server/channels/app/emoji.go @@ -131,7 +131,7 @@ func (a *App) UploadEmojiImage(c request.CTX, id string, imageData *multipart.Fi if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight { data := buf.Bytes() newbuf := bytes.NewBuffer(nil) - info, err := model.GetInfoForBytes(imageData.Filename, bytes.NewReader(data), len(data)) + info, err := getInfoForBytes(imageData.Filename, bytes.NewReader(data), len(data)) if err != nil { return err } diff --git a/server/channels/app/file.go b/server/channels/app/file.go index bd7ce30577..3d728e19ac 100644 --- a/server/channels/app/file.go +++ b/server/channels/app/file.go @@ -66,7 +66,7 @@ func (a *App) FileBackend() filestore.FileBackend { } func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError { - fileBackendSettings := settings.ToFileBackendSettings(false, false) + fileBackendSettings := filestore.NewFileBackendSettingsFromConfig(settings, false, false) err := fileBackendSettings.CheckMandatoryS3Fields() if err != nil { return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err) @@ -96,7 +96,7 @@ func (a *App) TestFileStoreConnection() *model.AppError { func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError { license := a.Srv().License() insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections - backend, err := filestore.NewFileBackend(cfg.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) + backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, license != nil && *license.Features.Compliance, insecure != nil && *insecure)) if err != nil { return model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -260,7 +260,7 @@ func (a *App) getInfoForFilename(post *model.Post, teamID, channelID, userID, ol return nil } - info, err := model.GetInfoForBytes(name, bytes.NewReader(data), len(data)) + info, err := getInfoForBytes(name, bytes.NewReader(data), len(data)) if err != nil { mlog.Warn( "Unable to fully decode file info when migrating post to use FileInfos", @@ -879,7 +879,7 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe channelID := filepath.Base(rawChannelId) userID := filepath.Base(rawUserId) - info, err := model.GetInfoForBytes(filename, bytes.NewReader(data), len(data)) + info, err := getInfoForBytes(filename, bytes.NewReader(data), len(data)) if err != nil { err.StatusCode = http.StatusBadRequest return nil, data, err diff --git a/server/channels/app/file_info.go b/server/channels/app/file_info.go new file mode 100644 index 0000000000..40b181afff --- /dev/null +++ b/server/channels/app/file_info.go @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package app + +import ( + "image" + "io" + "mime" + "net/http" + "path/filepath" + "strings" + + "github.com/mattermost/mattermost-server/server/v8/channels/utils/imgutils" + "github.com/mattermost/mattermost-server/server/v8/model" +) + +func getInfoForBytes(name string, data io.ReadSeeker, size int) (*model.FileInfo, *model.AppError) { + info := &model.FileInfo{ + Name: name, + Size: int64(size), + } + var err *model.AppError + + extension := strings.ToLower(filepath.Ext(name)) + info.MimeType = mime.TypeByExtension(extension) + + if extension != "" { + // The client expects a file extension without the leading period + info.Extension = extension[1:] + } else { + info.Extension = extension + } + + if info.IsImage() { + // Only set the width and height if it's actually an image that we can understand + if config, _, err := image.DecodeConfig(data); err == nil { + info.Width = config.Width + info.Height = config.Height + + if info.MimeType == "image/gif" { + // Just show the gif itself instead of a preview image for animated gifs + data.Seek(0, io.SeekStart) + frameCount, err := imgutils.CountGIFFrames(data) + if err != nil { + // Still return the rest of the info even though it doesn't appear to be an actual gif + info.HasPreviewImage = true + return info, model.NewAppError("getInfoForBytes", "app.file_info.get.gif.app_error", nil, "", http.StatusBadRequest).Wrap(err) + } + info.HasPreviewImage = frameCount == 1 + } else { + info.HasPreviewImage = true + } + } + } + + return info, err +} diff --git a/server/channels/app/file_info_test.go b/server/channels/app/file_info_test.go new file mode 100644 index 0000000000..f024d822a1 --- /dev/null +++ b/server/channels/app/file_info_test.go @@ -0,0 +1,148 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package app + +import ( + "bytes" + "encoding/base64" + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetInfoForFile(t *testing.T) { + fakeFile := make([]byte, 1000) + + pngFile, err := os.ReadFile("tests/test.png") + require.NoError(t, err, "Failed to load test.png") + + // base 64 encoded version of handtinywhite.gif from http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever + gifFile, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=") + + animatedGifFile, err := os.ReadFile("tests/testgif.gif") + require.NoError(t, err, "Failed to load testgif.gif") + + var ttc = []struct { + testName string + filename string + file []byte + usePrefixForMime bool + expectedExtension string + expectedSize int + expectedMime string + expectedWidth int + expectedHeight int + expectedHasPreviewImage bool + }{ + { + testName: "Text File", + filename: "file.txt", + file: fakeFile, + usePrefixForMime: true, + expectedExtension: "txt", + expectedSize: 1000, + expectedMime: "text/plain", + expectedWidth: 0, + expectedHeight: 0, + expectedHasPreviewImage: false, + }, + { + testName: "PNG file", + filename: "test.png", + file: pngFile, + usePrefixForMime: false, + expectedExtension: "png", + expectedSize: 279591, + expectedMime: "image/png", + expectedWidth: 408, + expectedHeight: 336, + expectedHasPreviewImage: true, + }, + { + testName: "Static Gif File", + filename: "handtinywhite.gif", + file: gifFile, + usePrefixForMime: false, + expectedExtension: "gif", + expectedSize: 35, + expectedMime: "image/gif", + expectedWidth: 1, + expectedHeight: 1, + expectedHasPreviewImage: true, + }, + { + testName: "Animated Gif File", + filename: "testgif.gif", + file: animatedGifFile, + usePrefixForMime: false, + expectedExtension: "gif", + expectedSize: 38689, + expectedMime: "image/gif", + expectedWidth: 118, + expectedHeight: 118, + expectedHasPreviewImage: false, + }, + { + testName: "No extension File", + filename: "filewithoutextension", + file: fakeFile, + usePrefixForMime: false, + expectedExtension: "", + expectedSize: 1000, + expectedMime: "", + expectedWidth: 0, + expectedHeight: 0, + expectedHasPreviewImage: false, + }, + { + // Always make the extension lower case to make it easier to use in other places + testName: "Uppercase extension File", + filename: "file.TXT", + file: fakeFile, + usePrefixForMime: true, + expectedExtension: "txt", + expectedSize: 1000, + expectedMime: "text/plain", + expectedWidth: 0, + expectedHeight: 0, + expectedHasPreviewImage: false, + }, + { + // Don't error out for image formats we don't support + testName: "Not supported File", + filename: "file.tif", + file: fakeFile, + usePrefixForMime: false, + expectedExtension: "tif", + expectedSize: 1000, + expectedMime: "image/tiff", + expectedWidth: 0, + expectedHeight: 0, + expectedHasPreviewImage: false, + }, + } + + for _, tc := range ttc { + t.Run(tc.testName, func(t *testing.T) { + info, appErr := getInfoForBytes(tc.filename, bytes.NewReader(tc.file), len(tc.file)) + require.Nil(t, appErr) + + assert.Equalf(t, tc.filename, info.Name, "Got incorrect filename: %v", info.Name) + assert.Equalf(t, tc.expectedExtension, info.Extension, "Got incorrect extension: %v", info.Extension) + assert.EqualValuesf(t, tc.expectedSize, info.Size, "Got incorrect size: %v", info.Size) + assert.Equalf(t, tc.expectedWidth, info.Width, "Got incorrect width: %v", info.Width) + assert.Equalf(t, tc.expectedHeight, info.Height, "Got incorrect height: %v", info.Height) + assert.Equalf(t, tc.expectedHasPreviewImage, info.HasPreviewImage, "Got incorrect has preview image: %v", info.HasPreviewImage) + + if tc.usePrefixForMime { + assert.Truef(t, strings.HasPrefix(info.MimeType, tc.expectedMime), "Got incorrect mime type: %v", info.MimeType) + } else { + assert.Equalf(t, tc.expectedMime, info.MimeType, "Got incorrect mime type: %v", info.MimeType) + } + }) + } +} diff --git a/server/model/oauthproviders/gitlab/gitlab.go b/server/channels/app/oauthproviders/gitlab/gitlab.go similarity index 100% rename from server/model/oauthproviders/gitlab/gitlab.go rename to server/channels/app/oauthproviders/gitlab/gitlab.go diff --git a/server/channels/app/platform/service.go b/server/channels/app/platform/service.go index 5fce0dcee0..4f3635b6a8 100644 --- a/server/channels/app/platform/service.go +++ b/server/channels/app/platform/service.go @@ -225,7 +225,7 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) { // Step 3: Initialize filestore if ps.filestore == nil { insecure := ps.Config().ServiceSettings.EnableInsecureOutgoingConnections - backend, err2 := filestore.NewFileBackend(ps.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) + backend, err2 := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&ps.Config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure)) if err2 != nil { return nil, fmt.Errorf("failed to initialize filebackend: %w", err2) } diff --git a/server/channels/app/plugin_api_test.go b/server/channels/app/plugin_api_test.go index 4c9fa41ead..c70a9d4d76 100644 --- a/server/channels/app/plugin_api_test.go +++ b/server/channels/app/plugin_api_test.go @@ -27,11 +27,11 @@ import ( "github.com/mattermost/mattermost-server/server/v8/channels/app/request" "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n" "github.com/mattermost/mattermost-server/server/v8/plugin" + "github.com/mattermost/mattermost-server/server/v8/plugin/utils" ) func getDefaultPluginSettingsSchema() string { diff --git a/server/channels/app/plugin_hooks_test.go b/server/channels/app/plugin_hooks_test.go index 3dc5e50ee3..192dc2e9b1 100644 --- a/server/channels/app/plugin_hooks_test.go +++ b/server/channels/app/plugin_hooks_test.go @@ -21,10 +21,10 @@ import ( "github.com/mattermost/mattermost-server/server/v8/channels/app/request" "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/plugin" "github.com/mattermost/mattermost-server/server/v8/plugin/plugintest" + "github.com/mattermost/mattermost-server/server/v8/plugin/utils" ) func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) { diff --git a/server/channels/app/plugin_install.go b/server/channels/app/plugin_install.go index 59ae9be613..c31c727ae5 100644 --- a/server/channels/app/plugin_install.go +++ b/server/channels/app/plugin_install.go @@ -45,8 +45,8 @@ import ( "github.com/blang/semver" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/model" + "github.com/mattermost/mattermost-server/server/v8/model/utils" "github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/plugin" diff --git a/server/channels/app/server.go b/server/channels/app/server.go index 7bfe79fd3c..ddfebd63c4 100644 --- a/server/channels/app/server.go +++ b/server/channels/app/server.go @@ -47,6 +47,7 @@ import ( "github.com/mattermost/mattermost-server/server/v8/channels/jobs/last_accessible_post" "github.com/mattermost/mattermost-server/server/v8/channels/jobs/migrations" "github.com/mattermost/mattermost-server/server/v8/channels/jobs/notify_admin" + "github.com/mattermost/mattermost-server/server/v8/channels/jobs/plugins" "github.com/mattermost/mattermost-server/server/v8/channels/jobs/product_notices" "github.com/mattermost/mattermost-server/server/v8/channels/jobs/resend_invitation_email" "github.com/mattermost/mattermost-server/server/v8/channels/product" @@ -70,7 +71,6 @@ import ( "github.com/mattermost/mattermost-server/server/v8/platform/shared/mail" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/platform/shared/templates" - "github.com/mattermost/mattermost-server/server/v8/plugin/scheduler" ) // declaring this as var to allow overriding in tests @@ -1500,8 +1500,8 @@ func (s *Server) initJobs() { s.Jobs.RegisterJobType( model.JobTypePlugins, - scheduler.MakeWorker(s.Jobs, New(ServerConnector(s.Channels()))), - scheduler.MakeScheduler(s.Jobs), + plugins.MakeWorker(s.Jobs, New(ServerConnector(s.Channels()))), + plugins.MakeScheduler(s.Jobs), ) s.Jobs.RegisterJobType( diff --git a/server/channels/app/user_test.go b/server/channels/app/user_test.go index da1938cd7e..caad0fe877 100644 --- a/server/channels/app/user_test.go +++ b/server/channels/app/user_test.go @@ -18,6 +18,7 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + oauthgitlab "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab" "github.com/mattermost/mattermost-server/server/v8/channels/app/request" "github.com/mattermost/mattermost-server/server/v8/channels/app/users" "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" @@ -26,7 +27,6 @@ import ( storemocks "github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks" "github.com/mattermost/mattermost-server/server/v8/channels/utils/testutils" "github.com/mattermost/mattermost-server/server/v8/model" - oauthgitlab "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab" ) func TestCreateOAuthUser(t *testing.T) { diff --git a/server/channels/app/users/profile_picture.go b/server/channels/app/users/profile_picture.go index 68eb890e0d..d6f96a5fe2 100644 --- a/server/channels/app/users/profile_picture.go +++ b/server/channels/app/users/profile_picture.go @@ -59,7 +59,7 @@ func (us *UserService) GetProfileImage(user *model.User) ([]byte, bool, error) { func (us *UserService) FileBackend() (filestore.FileBackend, error) { license := us.license() insecure := us.config().ServiceSettings.EnableInsecureOutgoingConnections - backend, err := filestore.NewFileBackend(us.config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure)) + backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&us.config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure)) if err != nil { return nil, err } diff --git a/server/plugin/scheduler/scheduler.go b/server/channels/jobs/plugins/scheduler.go similarity index 96% rename from server/plugin/scheduler/scheduler.go rename to server/channels/jobs/plugins/scheduler.go index 0640477a1c..ceae9897a3 100644 --- a/server/plugin/scheduler/scheduler.go +++ b/server/channels/jobs/plugins/scheduler.go @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -package scheduler +package plugins import ( "time" diff --git a/server/plugin/scheduler/worker.go b/server/channels/jobs/plugins/worker.go similarity index 99% rename from server/plugin/scheduler/worker.go rename to server/channels/jobs/plugins/worker.go index a03e5795d0..f824013120 100644 --- a/server/plugin/scheduler/worker.go +++ b/server/channels/jobs/plugins/worker.go @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -package scheduler +package plugins import ( "github.com/mattermost/mattermost-server/server/v8/channels/jobs" diff --git a/server/channels/testlib/resources.go b/server/channels/testlib/resources.go index 7142b965e5..c69aec9598 100644 --- a/server/channels/testlib/resources.go +++ b/server/channels/testlib/resources.go @@ -12,9 +12,9 @@ import ( "github.com/pkg/errors" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils" "github.com/mattermost/mattermost-server/server/v8/model" + "github.com/mattermost/mattermost-server/server/v8/model/utils" "github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore" ) diff --git a/server/channels/utils/file.go b/server/channels/utils/file.go index 4338f51508..495a554311 100644 --- a/server/channels/utils/file.go +++ b/server/channels/utils/file.go @@ -5,119 +5,9 @@ package utils import ( "errors" - "fmt" "io" - "os" - "path/filepath" ) -// CopyFile will copy a file from src path to dst path. -// Overwrites any existing files at dst. -// Permissions are copied from file at src to the new file at dst. -func CopyFile(src, dst string) (err error) { - in, err := os.Open(src) - if err != nil { - return - } - defer in.Close() - - if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { - return - } - out, err := os.Create(dst) - if err != nil { - return - } - defer func() { - if e := out.Close(); e != nil { - err = e - } - }() - - _, err = io.Copy(out, in) - if err != nil { - return - } - - err = out.Sync() - if err != nil { - return - } - - stat, err := os.Stat(src) - if err != nil { - return - } - err = os.Chmod(dst, stat.Mode()) - if err != nil { - return - } - - return -} - -// CopyDir will copy a directory and all contained files and directories. -// src must exist and dst must not exist. -// Permissions are preserved when possible. Symlinks are skipped. -func CopyDir(src string, dst string) (err error) { - src = filepath.Clean(src) - dst = filepath.Clean(dst) - - stat, err := os.Stat(src) - if err != nil { - return - } - if !stat.IsDir() { - return fmt.Errorf("source must be a directory") - } - - _, err = os.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return - } - if err == nil { - return fmt.Errorf("destination already exists") - } - - err = os.MkdirAll(dst, stat.Mode()) - if err != nil { - return - } - - items, err := os.ReadDir(src) - if err != nil { - return - } - - for _, item := range items { - srcPath := filepath.Join(src, item.Name()) - dstPath := filepath.Join(dst, item.Name()) - - if item.IsDir() { - err = CopyDir(srcPath, dstPath) - if err != nil { - return - } - } else { - info, ierr := item.Info() - if ierr != nil { - continue - } - - if info.Mode()&os.ModeSymlink != 0 { - continue - } - - err = CopyFile(srcPath, dstPath) - if err != nil { - return - } - } - } - - return -} - var SizeLimitExceeded = errors.New("Size limit exceeded") type LimitedReaderWithError struct { diff --git a/server/channels/utils/file_test.go b/server/channels/utils/file_test.go index f41c092c42..9b5bccb0bf 100644 --- a/server/channels/utils/file_test.go +++ b/server/channels/utils/file_test.go @@ -7,63 +7,11 @@ import ( "bytes" "crypto/rand" "io" - "os" - "path/filepath" "testing" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCopyDir(t *testing.T) { - srcDir, err := os.MkdirTemp("", "src") - require.NoError(t, err) - defer os.RemoveAll(srcDir) - - dstParentDir, err := os.MkdirTemp("", "dstparent") - require.NoError(t, err) - defer os.RemoveAll(dstParentDir) - - dstDir := filepath.Join(dstParentDir, "dst") - - tempFile := "temp.txt" - err = os.WriteFile(filepath.Join(srcDir, tempFile), []byte("test file"), 0655) - require.NoError(t, err) - - childDir := "child" - err = os.Mkdir(filepath.Join(srcDir, childDir), 0777) - require.NoError(t, err) - - childTempFile := "childtemp.txt" - err = os.WriteFile(filepath.Join(srcDir, childDir, childTempFile), []byte("test file"), 0755) - require.NoError(t, err) - - err = CopyDir(srcDir, dstDir) - assert.NoError(t, err) - - stat, err := os.Stat(filepath.Join(dstDir, tempFile)) - assert.NoError(t, err) - assert.Equal(t, uint32(0655), uint32(stat.Mode())) - assert.False(t, stat.IsDir()) - data, err := os.ReadFile(filepath.Join(dstDir, tempFile)) - assert.NoError(t, err) - assert.Equal(t, "test file", string(data)) - - stat, err = os.Stat(filepath.Join(dstDir, childDir)) - assert.NoError(t, err) - assert.True(t, stat.IsDir()) - - stat, err = os.Stat(filepath.Join(dstDir, childDir, childTempFile)) - assert.NoError(t, err) - assert.Equal(t, uint32(0755), uint32(stat.Mode())) - assert.False(t, stat.IsDir()) - data, err = os.ReadFile(filepath.Join(dstDir, childDir, childTempFile)) - assert.NoError(t, err) - assert.Equal(t, "test file", string(data)) - - err = CopyDir(srcDir, dstDir) - assert.Error(t, err) -} func TestLimitedReaderWithError(t *testing.T) { t.Run("read less than max size", func(t *testing.T) { maxBytes := 10 diff --git a/server/channels/web/web_test.go b/server/channels/web/web_test.go index c6d9d46fbd..e0a57ce000 100644 --- a/server/channels/web/web_test.go +++ b/server/channels/web/web_test.go @@ -21,11 +21,11 @@ import ( "github.com/mattermost/mattermost-server/server/v8/channels/app/request" "github.com/mattermost/mattermost-server/server/v8/channels/store/localcachelayer" "github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/config" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" "github.com/mattermost/mattermost-server/server/v8/plugin" + "github.com/mattermost/mattermost-server/server/v8/plugin/utils" ) var apiClient *model.Client4 diff --git a/server/cmd/mattermost/main.go b/server/cmd/mattermost/main.go index a0b31a2dc2..f8876f4a48 100644 --- a/server/cmd/mattermost/main.go +++ b/server/cmd/mattermost/main.go @@ -10,7 +10,7 @@ import ( // Import and register app layer slash commands _ "github.com/mattermost/mattermost-server/server/v8/channels/app/slashcommands" // Plugins - _ "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab" + _ "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab" // Enterprise Imports _ "github.com/mattermost/mattermost-server/server/v8/channels/imports" diff --git a/server/config/store.go b/server/config/store.go index ca9cfca815..00b9ce3fef 100644 --- a/server/config/store.go +++ b/server/config/store.go @@ -11,8 +11,8 @@ import ( "github.com/pkg/errors" - "github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils" "github.com/mattermost/mattermost-server/server/v8/model" + "github.com/mattermost/mattermost-server/server/v8/model/utils" "github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n" ) @@ -258,7 +258,7 @@ func (s *Store) Load() error { loadedCfg := &model.Config{} if len(configBytes) != 0 { if err = json.Unmarshal(configBytes, &loadedCfg); err != nil { - return jsonutils.HumanizeJSONError(err, configBytes) + return utils.HumanizeJSONError(err, configBytes) } } diff --git a/server/i18n/en.json b/server/i18n/en.json index 2f5f53258c..9f9a80e7b8 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -5127,6 +5127,10 @@ "id": "app.file_info.get.app_error", "translation": "Unable to get the file info." }, + { + "id": "app.file_info.get.gif.app_error", + "translation": "Could not decode gif." + }, { "id": "app.file_info.get_for_post.app_error", "translation": "Unable to get the file info for the post." @@ -9091,10 +9095,6 @@ "id": "model.emoji.user_id.app_error", "translation": "Invalid creator id." }, - { - "id": "model.file_info.get.gif.app_error", - "translation": "Could not decode gif." - }, { "id": "model.file_info.is_valid.create_at.app_error", "translation": "Invalid value for create_at." diff --git a/server/model/command_response.go b/server/model/command_response.go index 4fd810f373..fb5c34ca87 100644 --- a/server/model/command_response.go +++ b/server/model/command_response.go @@ -8,7 +8,7 @@ import ( "io" "strings" - "github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils" + "github.com/mattermost/mattermost-server/server/v8/model/utils" ) const ( @@ -56,7 +56,7 @@ func CommandResponseFromJSON(data io.Reader) (*CommandResponse, error) { var o CommandResponse err = json.Unmarshal(b, &o) if err != nil { - return nil, jsonutils.HumanizeJSONError(err, b) + return nil, utils.HumanizeJSONError(err, b) } o.Attachments = StringifySlackFieldValue(o.Attachments) diff --git a/server/model/config.go b/server/model/config.go index d5a0fcf868..57ec796119 100644 --- a/server/model/config.go +++ b/server/model/config.go @@ -21,7 +21,6 @@ import ( "github.com/mattermost/ldap" - "github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" ) @@ -1573,30 +1572,6 @@ func (s *FileSettings) SetDefaults(isUpdate bool) { } } -func (s *FileSettings) ToFileBackendSettings(enableComplianceFeature bool, skipVerify bool) filestore.FileBackendSettings { - if *s.DriverName == ImageDriverLocal { - return filestore.FileBackendSettings{ - DriverName: *s.DriverName, - Directory: *s.Directory, - } - } - return filestore.FileBackendSettings{ - DriverName: *s.DriverName, - AmazonS3AccessKeyId: *s.AmazonS3AccessKeyId, - AmazonS3SecretAccessKey: *s.AmazonS3SecretAccessKey, - AmazonS3Bucket: *s.AmazonS3Bucket, - AmazonS3PathPrefix: *s.AmazonS3PathPrefix, - AmazonS3Region: *s.AmazonS3Region, - AmazonS3Endpoint: *s.AmazonS3Endpoint, - AmazonS3SSL: s.AmazonS3SSL == nil || *s.AmazonS3SSL, - AmazonS3SignV2: s.AmazonS3SignV2 != nil && *s.AmazonS3SignV2, - AmazonS3SSE: s.AmazonS3SSE != nil && *s.AmazonS3SSE && enableComplianceFeature, - AmazonS3Trace: s.AmazonS3Trace != nil && *s.AmazonS3Trace, - AmazonS3RequestTimeoutMilliseconds: *s.AmazonS3RequestTimeoutMilliseconds, - SkipVerify: skipVerify, - } -} - type EmailSettings struct { EnableSignUpWithEmail *bool `access:"authentication_email"` EnableSignInWithEmail *bool `access:"authentication_email"` diff --git a/server/model/file_info.go b/server/model/file_info.go index 99d9af8fed..1b51fed507 100644 --- a/server/model/file_info.go +++ b/server/model/file_info.go @@ -4,14 +4,10 @@ package model import ( - "image" - "io" "mime" "net/http" "path/filepath" "strings" - - "github.com/mattermost/mattermost-server/server/v8/channels/utils/imgutils" ) const ( @@ -150,48 +146,6 @@ func NewInfo(name string) *FileInfo { return info } -func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *AppError) { - info := &FileInfo{ - Name: name, - Size: int64(size), - } - var err *AppError - - extension := strings.ToLower(filepath.Ext(name)) - info.MimeType = mime.TypeByExtension(extension) - - if extension != "" { - // The client expects a file extension without the leading period - info.Extension = extension[1:] - } else { - info.Extension = extension - } - - if info.IsImage() { - // Only set the width and height if it's actually an image that we can understand - if config, _, err := image.DecodeConfig(data); err == nil { - info.Width = config.Width - info.Height = config.Height - - if info.MimeType == "image/gif" { - // Just show the gif itself instead of a preview image for animated gifs - data.Seek(0, io.SeekStart) - frameCount, err := imgutils.CountGIFFrames(data) - if err != nil { - // Still return the rest of the info even though it doesn't appear to be an actual gif - info.HasPreviewImage = true - return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "", http.StatusBadRequest).Wrap(err) - } - info.HasPreviewImage = frameCount == 1 - } else { - info.HasPreviewImage = true - } - } - } - - return info, err -} - func GetEtagForFileInfos(infos []*FileInfo) string { if len(infos) == 0 { return Etag() diff --git a/server/model/file_info_test.go b/server/model/file_info_test.go index 997e939879..235397876f 100644 --- a/server/model/file_info_test.go +++ b/server/model/file_info_test.go @@ -4,16 +4,11 @@ package model import ( - "bytes" - "encoding/base64" _ "image/gif" _ "image/png" - "os" - "strings" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestFileInfoIsValid(t *testing.T) { @@ -72,136 +67,3 @@ func TestFileInfoIsImage(t *testing.T) { assert.False(t, info.IsImage(), "Text file should not be considered as an image") }) } - -func TestGetInfoForFile(t *testing.T) { - fakeFile := make([]byte, 1000) - - pngFile, err := os.ReadFile("../tests/test.png") - require.NoError(t, err, "Failed to load test.png") - - // base 64 encoded version of handtinywhite.gif from http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever - gifFile, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=") - - animatedGifFile, err := os.ReadFile("../tests/testgif.gif") - require.NoError(t, err, "Failed to load testgif.gif") - - var ttc = []struct { - testName string - filename string - file []byte - usePrefixForMime bool - expectedExtension string - expectedSize int - expectedMime string - expectedWidth int - expectedHeight int - expectedHasPreviewImage bool - }{ - { - testName: "Text File", - filename: "file.txt", - file: fakeFile, - usePrefixForMime: true, - expectedExtension: "txt", - expectedSize: 1000, - expectedMime: "text/plain", - expectedWidth: 0, - expectedHeight: 0, - expectedHasPreviewImage: false, - }, - { - testName: "PNG file", - filename: "test.png", - file: pngFile, - usePrefixForMime: false, - expectedExtension: "png", - expectedSize: 279591, - expectedMime: "image/png", - expectedWidth: 408, - expectedHeight: 336, - expectedHasPreviewImage: true, - }, - { - testName: "Static Gif File", - filename: "handtinywhite.gif", - file: gifFile, - usePrefixForMime: false, - expectedExtension: "gif", - expectedSize: 35, - expectedMime: "image/gif", - expectedWidth: 1, - expectedHeight: 1, - expectedHasPreviewImage: true, - }, - { - testName: "Animated Gif File", - filename: "testgif.gif", - file: animatedGifFile, - usePrefixForMime: false, - expectedExtension: "gif", - expectedSize: 38689, - expectedMime: "image/gif", - expectedWidth: 118, - expectedHeight: 118, - expectedHasPreviewImage: false, - }, - { - testName: "No extension File", - filename: "filewithoutextension", - file: fakeFile, - usePrefixForMime: false, - expectedExtension: "", - expectedSize: 1000, - expectedMime: "", - expectedWidth: 0, - expectedHeight: 0, - expectedHasPreviewImage: false, - }, - { - // Always make the extension lower case to make it easier to use in other places - testName: "Uppercase extension File", - filename: "file.TXT", - file: fakeFile, - usePrefixForMime: true, - expectedExtension: "txt", - expectedSize: 1000, - expectedMime: "text/plain", - expectedWidth: 0, - expectedHeight: 0, - expectedHasPreviewImage: false, - }, - { - // Don't error out for image formats we don't support - testName: "Not supported File", - filename: "file.tif", - file: fakeFile, - usePrefixForMime: false, - expectedExtension: "tif", - expectedSize: 1000, - expectedMime: "image/tiff", - expectedWidth: 0, - expectedHeight: 0, - expectedHasPreviewImage: false, - }, - } - - for _, tc := range ttc { - t.Run(tc.testName, func(t *testing.T) { - info, appErr := GetInfoForBytes(tc.filename, bytes.NewReader(tc.file), len(tc.file)) - require.Nil(t, appErr) - - assert.Equalf(t, tc.filename, info.Name, "Got incorrect filename: %v", info.Name) - assert.Equalf(t, tc.expectedExtension, info.Extension, "Got incorrect extension: %v", info.Extension) - assert.EqualValuesf(t, tc.expectedSize, info.Size, "Got incorrect size: %v", info.Size) - assert.Equalf(t, tc.expectedWidth, info.Width, "Got incorrect width: %v", info.Width) - assert.Equalf(t, tc.expectedHeight, info.Height, "Got incorrect height: %v", info.Height) - assert.Equalf(t, tc.expectedHasPreviewImage, info.HasPreviewImage, "Got incorrect has preview image: %v", info.HasPreviewImage) - - if tc.usePrefixForMime { - assert.Truef(t, strings.HasPrefix(info.MimeType, tc.expectedMime), "Got incorrect mime type: %v", info.MimeType) - } else { - assert.Equalf(t, tc.expectedMime, info.MimeType, "Got incorrect mime type: %v", info.MimeType) - } - }) - } -} diff --git a/server/model/utils/file.go b/server/model/utils/file.go new file mode 100644 index 0000000000..087626bbb1 --- /dev/null +++ b/server/model/utils/file.go @@ -0,0 +1,118 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package utils + +import ( + "fmt" + "io" + "os" + "path/filepath" +) + +// CopyFile will copy a file from src path to dst path. +// Overwrites any existing files at dst. +// Permissions are copied from file at src to the new file at dst. +func CopyFile(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return + } + defer in.Close() + + if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { + return + } + out, err := os.Create(dst) + if err != nil { + return + } + defer func() { + if e := out.Close(); e != nil { + err = e + } + }() + + _, err = io.Copy(out, in) + if err != nil { + return + } + + err = out.Sync() + if err != nil { + return + } + + stat, err := os.Stat(src) + if err != nil { + return + } + err = os.Chmod(dst, stat.Mode()) + if err != nil { + return + } + + return +} + +// CopyDir will copy a directory and all contained files and directories. +// src must exist and dst must not exist. +// Permissions are preserved when possible. Symlinks are skipped. +func CopyDir(src string, dst string) (err error) { + src = filepath.Clean(src) + dst = filepath.Clean(dst) + + stat, err := os.Stat(src) + if err != nil { + return + } + if !stat.IsDir() { + return fmt.Errorf("source must be a directory") + } + + _, err = os.Stat(dst) + if err != nil && !os.IsNotExist(err) { + return + } + if err == nil { + return fmt.Errorf("destination already exists") + } + + err = os.MkdirAll(dst, stat.Mode()) + if err != nil { + return + } + + items, err := os.ReadDir(src) + if err != nil { + return + } + + for _, item := range items { + srcPath := filepath.Join(src, item.Name()) + dstPath := filepath.Join(dst, item.Name()) + + if item.IsDir() { + err = CopyDir(srcPath, dstPath) + if err != nil { + return + } + } else { + info, ierr := item.Info() + if ierr != nil { + continue + } + + if info.Mode()&os.ModeSymlink != 0 { + continue + } + + err = CopyFile(srcPath, dstPath) + if err != nil { + return + } + } + } + + return +} diff --git a/server/model/utils/file_test.go b/server/model/utils/file_test.go new file mode 100644 index 0000000000..753b0399ea --- /dev/null +++ b/server/model/utils/file_test.go @@ -0,0 +1,63 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package utils + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCopyDir(t *testing.T) { + srcDir, err := os.MkdirTemp("", "src") + require.NoError(t, err) + defer os.RemoveAll(srcDir) + + dstParentDir, err := os.MkdirTemp("", "dstparent") + require.NoError(t, err) + defer os.RemoveAll(dstParentDir) + + dstDir := filepath.Join(dstParentDir, "dst") + + tempFile := "temp.txt" + err = os.WriteFile(filepath.Join(srcDir, tempFile), []byte("test file"), 0655) + require.NoError(t, err) + + childDir := "child" + err = os.Mkdir(filepath.Join(srcDir, childDir), 0777) + require.NoError(t, err) + + childTempFile := "childtemp.txt" + err = os.WriteFile(filepath.Join(srcDir, childDir, childTempFile), []byte("test file"), 0755) + require.NoError(t, err) + + err = CopyDir(srcDir, dstDir) + assert.NoError(t, err) + + stat, err := os.Stat(filepath.Join(dstDir, tempFile)) + assert.NoError(t, err) + assert.Equal(t, uint32(0655), uint32(stat.Mode())) + assert.False(t, stat.IsDir()) + data, err := os.ReadFile(filepath.Join(dstDir, tempFile)) + assert.NoError(t, err) + assert.Equal(t, "test file", string(data)) + + stat, err = os.Stat(filepath.Join(dstDir, childDir)) + assert.NoError(t, err) + assert.True(t, stat.IsDir()) + + stat, err = os.Stat(filepath.Join(dstDir, childDir, childTempFile)) + assert.NoError(t, err) + assert.Equal(t, uint32(0755), uint32(stat.Mode())) + assert.False(t, stat.IsDir()) + data, err = os.ReadFile(filepath.Join(dstDir, childDir, childTempFile)) + assert.NoError(t, err) + assert.Equal(t, "test file", string(data)) + + err = CopyDir(srcDir, dstDir) + assert.Error(t, err) +} diff --git a/server/channels/utils/jsonutils/json.go b/server/model/utils/json.go similarity index 98% rename from server/channels/utils/jsonutils/json.go rename to server/model/utils/json.go index 9d5e787217..39d8646922 100644 --- a/server/channels/utils/jsonutils/json.go +++ b/server/model/utils/json.go @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -package jsonutils +package utils import ( "bytes" diff --git a/server/channels/utils/jsonutils/json_test.go b/server/model/utils/json_test.go similarity index 85% rename from server/channels/utils/jsonutils/json_test.go rename to server/model/utils/json_test.go index 30ed5f7104..5686684ca5 100644 --- a/server/channels/utils/jsonutils/json_test.go +++ b/server/model/utils/json_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -package jsonutils_test +package utils_test import ( "encoding/json" @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils" + "github.com/mattermost/mattermost-server/server/v8/model/utils" ) func TestHumanizeJsonError(t *testing.T) { @@ -56,14 +56,14 @@ func TestHumanizeJsonError(t *testing.T) { Struct: "struct", Field: "field", }, - "parsing error at line 3, character 4: json: cannot unmarshal bool into Go struct field struct.field of type jsonutils_test.testType", + "parsing error at line 3, character 4: json: cannot unmarshal bool into Go struct field struct.field of type utils_test.testType", }, } for _, testCase := range testCases { testCase := testCase t.Run(testCase.Description, func(t *testing.T) { - actual := jsonutils.HumanizeJSONError(testCase.Err, testCase.Data) + actual := utils.HumanizeJSONError(testCase.Err, testCase.Data) if testCase.ExpectedErr == "" { assert.NoError(t, actual) } else { @@ -81,7 +81,7 @@ func TestNewHumanizedJSONError(t *testing.T) { Data []byte Offset int64 Err error - Expected *jsonutils.HumanizedJSONError + Expected *utils.HumanizedJSONError }{ { "nil error", @@ -95,7 +95,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), -1, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "invalid offset -1"), }, }, @@ -104,7 +104,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 0, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 1"), Line: 1, Character: 1, @@ -115,7 +115,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 5, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 6"), Line: 1, Character: 6, @@ -126,7 +126,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 6, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 7"), Line: 1, Character: 7, @@ -137,7 +137,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 7, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 1"), Line: 2, Character: 1, @@ -148,7 +148,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 12, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 6"), Line: 2, Character: 6, @@ -159,7 +159,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 13, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 7"), Line: 2, Character: 7, @@ -170,7 +170,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 17, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 4"), Line: 3, Character: 4, @@ -181,7 +181,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 19, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 6"), Line: 3, Character: 6, @@ -192,7 +192,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 20, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 7"), Line: 3, Character: 7, @@ -203,7 +203,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3\n"), 21, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "parsing error at line 4, character 1"), Line: 4, Character: 1, @@ -214,7 +214,7 @@ func TestNewHumanizedJSONError(t *testing.T) { []byte("line 1\nline 2\nline 3"), 21, errors.New("message"), - &jsonutils.HumanizedJSONError{ + &utils.HumanizedJSONError{ Err: errors.Wrap(errors.New("message"), "invalid offset 21"), }, }, @@ -223,7 +223,7 @@ func TestNewHumanizedJSONError(t *testing.T) { for _, testCase := range testCases { testCase := testCase t.Run(testCase.Description, func(t *testing.T) { - actual := jsonutils.NewHumanizedJSONError(testCase.Err, testCase.Data, testCase.Offset) + actual := utils.NewHumanizedJSONError(testCase.Err, testCase.Data, testCase.Offset) if testCase.Expected != nil && actual.Err != nil { if assert.EqualValues(t, testCase.Expected.Err.Error(), actual.Err.Error()) { actual.Err = testCase.Expected.Err diff --git a/server/platform/shared/filestore/filesstore.go b/server/platform/shared/filestore/filesstore.go index fd1017591c..cc6ce7c8bd 100644 --- a/server/platform/shared/filestore/filesstore.go +++ b/server/platform/shared/filestore/filesstore.go @@ -8,6 +8,7 @@ import ( "io" "time" + "github.com/mattermost/mattermost-server/server/v8/model" "github.com/pkg/errors" ) @@ -57,6 +58,30 @@ type FileBackendSettings struct { AmazonS3RequestTimeoutMilliseconds int64 } +func NewFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings { + if *fileSettings.DriverName == model.ImageDriverLocal { + return FileBackendSettings{ + DriverName: *fileSettings.DriverName, + Directory: *fileSettings.Directory, + } + } + return FileBackendSettings{ + DriverName: *fileSettings.DriverName, + AmazonS3AccessKeyId: *fileSettings.AmazonS3AccessKeyId, + AmazonS3SecretAccessKey: *fileSettings.AmazonS3SecretAccessKey, + AmazonS3Bucket: *fileSettings.AmazonS3Bucket, + AmazonS3PathPrefix: *fileSettings.AmazonS3PathPrefix, + AmazonS3Region: *fileSettings.AmazonS3Region, + AmazonS3Endpoint: *fileSettings.AmazonS3Endpoint, + AmazonS3SSL: fileSettings.AmazonS3SSL == nil || *fileSettings.AmazonS3SSL, + AmazonS3SignV2: fileSettings.AmazonS3SignV2 != nil && *fileSettings.AmazonS3SignV2, + AmazonS3SSE: fileSettings.AmazonS3SSE != nil && *fileSettings.AmazonS3SSE && enableComplianceFeature, + AmazonS3Trace: fileSettings.AmazonS3Trace != nil && *fileSettings.AmazonS3Trace, + AmazonS3RequestTimeoutMilliseconds: *fileSettings.AmazonS3RequestTimeoutMilliseconds, + SkipVerify: skipVerify, + } +} + func (settings *FileBackendSettings) CheckMandatoryS3Fields() error { if settings.AmazonS3Bucket == "" { return errors.New("missing s3 bucket settings") diff --git a/server/platform/shared/filestore/s3store_test.go b/server/platform/shared/filestore/s3store_test.go index c664dbf77f..f71dfb6572 100644 --- a/server/platform/shared/filestore/s3store_test.go +++ b/server/platform/shared/filestore/s3store_test.go @@ -18,17 +18,11 @@ import ( "testing" "time" + "github.com/mattermost/mattermost-server/server/v8/model" s3 "github.com/minio/minio-go/v7" "github.com/stretchr/testify/require" ) -// Copied from model/config.go to avoid an import cycle -const ( - MinioAccessKey = "minioaccesskey" - MinioSecretKey = "miniosecretkey" - ImageDriverS3 = "amazons3" -) - func TestCheckMandatoryS3Fields(t *testing.T) { cfg := FileBackendSettings{} @@ -69,9 +63,9 @@ func TestMakeBucket(t *testing.T) { bucketName = strings.Replace(bucketName, "/", "", -1) cfg := FileBackendSettings{ - DriverName: ImageDriverS3, - AmazonS3AccessKeyId: MinioAccessKey, - AmazonS3SecretAccessKey: MinioSecretKey, + DriverName: model.ImageDriverS3, + AmazonS3AccessKeyId: model.MinioAccessKey, + AmazonS3SecretAccessKey: model.MinioSecretKey, AmazonS3Bucket: bucketName, AmazonS3Endpoint: s3Endpoint, AmazonS3Region: "", @@ -110,9 +104,9 @@ func TestTimeout(t *testing.T) { bucketName = strings.Replace(bucketName, "/", "", -1) cfg := FileBackendSettings{ - DriverName: ImageDriverS3, - AmazonS3AccessKeyId: MinioAccessKey, - AmazonS3SecretAccessKey: MinioSecretKey, + DriverName: model.ImageDriverS3, + AmazonS3AccessKeyId: model.MinioAccessKey, + AmazonS3SecretAccessKey: model.MinioSecretKey, AmazonS3Bucket: bucketName, AmazonS3Endpoint: s3Endpoint, AmazonS3Region: "", @@ -171,9 +165,9 @@ func TestInsecureMakeBucket(t *testing.T) { bucketName = strings.Replace(bucketName, "/", "", -1) cfg := FileBackendSettings{ - DriverName: ImageDriverS3, - AmazonS3AccessKeyId: MinioAccessKey, - AmazonS3SecretAccessKey: MinioSecretKey, + DriverName: model.ImageDriverS3, + AmazonS3AccessKeyId: model.MinioAccessKey, + AmazonS3SecretAccessKey: model.MinioSecretKey, AmazonS3Bucket: bucketName, AmazonS3Endpoint: proxySelfSignedHTTPS.URL[8:], AmazonS3Region: "", diff --git a/server/platform/shared/markdown/html_entities.go b/server/platform/shared/markdown/html_entities.go index e94cebb9f7..24c69a50fa 100644 --- a/server/platform/shared/markdown/html_entities.go +++ b/server/platform/shared/markdown/html_entities.go @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. +//nolint:misspell package markdown var htmlEntities = map[string]string{ diff --git a/server/plugin/api_timer_layer_generated.go b/server/plugin/api_timer_layer_generated.go index 0f1ab22ff7..2fd9e8368d 100644 --- a/server/plugin/api_timer_layer_generated.go +++ b/server/plugin/api_timer_layer_generated.go @@ -11,14 +11,13 @@ import ( "net/http" timePkg "time" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" "github.com/mattermost/mattermost-server/server/v8/model" ) type apiTimerLayer struct { pluginID string apiImpl API - metrics einterfaces.MetricsInterface + metrics metricsInterface } func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) { diff --git a/server/plugin/environment.go b/server/plugin/environment.go index 2cdcbdd90c..e7254acc36 100644 --- a/server/plugin/environment.go +++ b/server/plugin/environment.go @@ -13,9 +13,8 @@ import ( "github.com/pkg/errors" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/model" + "github.com/mattermost/mattermost-server/server/v8/model/utils" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" ) @@ -52,7 +51,7 @@ type Environment struct { registeredPlugins sync.Map pluginHealthCheckJob *PluginHealthCheckJob logger *mlog.Logger - metrics einterfaces.MetricsInterface + metrics metricsInterface newAPIImpl apiImplCreatorFunc dbDriver Driver pluginDir string @@ -67,7 +66,7 @@ func NewEnvironment( pluginDir string, webappPluginDir string, logger *mlog.Logger, - metrics einterfaces.MetricsInterface, + metrics metricsInterface, ) (*Environment, error) { return &Environment{ logger: logger, diff --git a/server/plugin/health_check_test.go b/server/plugin/health_check_test.go index 36cb728545..72e9a926a3 100644 --- a/server/plugin/health_check_test.go +++ b/server/plugin/health_check_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/require" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" + "github.com/mattermost/mattermost-server/server/v8/plugin/utils" ) func TestPluginHealthCheck(t *testing.T) { diff --git a/server/plugin/hooks_timer_layer_generated.go b/server/plugin/hooks_timer_layer_generated.go index 393e6e6a91..3dfe0f085c 100644 --- a/server/plugin/hooks_timer_layer_generated.go +++ b/server/plugin/hooks_timer_layer_generated.go @@ -11,14 +11,13 @@ import ( "net/http" timePkg "time" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" "github.com/mattermost/mattermost-server/server/v8/model" ) type hooksTimerLayer struct { pluginID string hooksImpl Hooks - metrics einterfaces.MetricsInterface + metrics metricsInterface } func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) { diff --git a/server/plugin/interface_generator/main.go b/server/plugin/interface_generator/main.go index 2a02c26d41..9992234e77 100644 --- a/server/plugin/interface_generator/main.go +++ b/server/plugin/interface_generator/main.go @@ -453,14 +453,13 @@ import ( "net/http" timePkg "time" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" "github.com/mattermost/mattermost-server/server/v8/model" ) type apiTimerLayer struct { pluginID string apiImpl API - metrics einterfaces.MetricsInterface + metrics metricsInterface } func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) { @@ -495,14 +494,13 @@ import ( "net/http" timePkg "time" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" "github.com/mattermost/mattermost-server/server/v8/model" ) type hooksTimerLayer struct { pluginID string hooksImpl Hooks - metrics einterfaces.MetricsInterface + metrics metricsInterface } func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) { diff --git a/server/plugin/metrics.go b/server/plugin/metrics.go new file mode 100644 index 0000000000..b9c2f0010b --- /dev/null +++ b/server/plugin/metrics.go @@ -0,0 +1,11 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package plugin + +type metricsInterface interface { + ObservePluginHookDuration(pluginID, hookName string, success bool, elapsed float64) + ObservePluginMultiHookIterationDuration(pluginID string, elapsed float64) + ObservePluginMultiHookDuration(elapsed float64) + ObservePluginAPIDuration(pluginID, apiName string, success bool, elapsed float64) +} diff --git a/server/plugin/supervisor.go b/server/plugin/supervisor.go index 415ee3820c..ce0dcecab5 100644 --- a/server/plugin/supervisor.go +++ b/server/plugin/supervisor.go @@ -18,7 +18,6 @@ import ( plugin "github.com/hashicorp/go-plugin" "github.com/pkg/errors" - "github.com/mattermost/mattermost-server/server/v8/channels/einterfaces" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" ) @@ -32,7 +31,7 @@ type supervisor struct { hooksClient *hooksRPCClient } -func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, parentLogger *mlog.Logger, metrics einterfaces.MetricsInterface) (retSupervisor *supervisor, retErr error) { +func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, parentLogger *mlog.Logger, metrics metricsInterface) (retSupervisor *supervisor, retErr error) { sup := supervisor{} defer func() { if retErr != nil { diff --git a/server/plugin/supervisor_test.go b/server/plugin/supervisor_test.go index af9a55d759..29ee285842 100644 --- a/server/plugin/supervisor_test.go +++ b/server/plugin/supervisor_test.go @@ -11,9 +11,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/mattermost/mattermost-server/server/v8/channels/utils" "github.com/mattermost/mattermost-server/server/v8/model" "github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog" + "github.com/mattermost/mattermost-server/server/v8/plugin/utils" ) func TestSupervisor(t *testing.T) { diff --git a/server/channels/utils/test_files_compiler.go b/server/plugin/utils/test_files_compiler.go similarity index 100% rename from server/channels/utils/test_files_compiler.go rename to server/plugin/utils/test_files_compiler.go