mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Always require signatures for prepackaged plugins We have always required signatures for packages installed via the marketplace -- whether remotely satisfied, or sourced from the prepackaged plugin cache. However, prepackaged plugins discovered and automatically installed on startup did not require a valid signature. Since we already ship signatures for all Mattermost-authored prepackaged plugins, it's easy to simply start requiring this. Distributions of Mattermost that bundle their own prepackaged plugins will have to include their own signatures. This in turn requires distributing and configuring Mattermost with a custom public key via `PluginSettings.SignaturePublicKeyFiles`. Note that this enhanced security is neutered with a deployment that uses a file-based `config.json`, as any exploit that allows appending to the prepackaged plugins cache probably also allows modifying `config.json` to register a new public key. A [database-based config](https://docs.mattermost.com/configure/configuration-in-your-database.html) is recommended. Finally, we already support an optional setting `PluginSettings.RequirePluginSignature` to always require a plugin signature, although this effectively disables plugin uploads and requires extra effort to deploy the corresponding signature. In environments where only prepackaged plugins are used, this setting is ideal. Fixes: https://mattermost.atlassian.net/browse/MM-64627 * setup dev key, expect no plugins if sig fails * Fix shadow variable errors in test helpers Pre-declare signaturePublicKey variable in loops to avoid shadowing the outer err variable used in error handling. * Replace PrepackagedPlugin.Signature with SignaturePath for memory efficiency - Changed PrepackagedPlugin struct to use SignaturePath string instead of Signature []byte - Updated buildPrepackagedPlugin to use file descriptor instead of reading signature into memory - Modified plugin installation and persistence to read from signature file paths - Updated all tests to check SignaturePath instead of Signature field - Removed unused bytes import from plugin.go This change reduces memory usage by storing file paths instead of signature data in memory while maintaining the same security verification functionality. --------- Co-authored-by: Hanzo Dev <[email protected]>
1479 lines
44 KiB
Go
1479 lines
44 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
s3 "github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
"github.com/mattermost/mattermost/server/v8/channels/web"
|
|
"github.com/mattermost/mattermost/server/v8/channels/wsapi"
|
|
"github.com/mattermost/mattermost/server/v8/config"
|
|
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
|
)
|
|
|
|
type TestHelper struct {
|
|
App *app.App
|
|
Server *app.Server
|
|
ConfigStore *config.Store
|
|
Store store.Store
|
|
|
|
Context *request.Context
|
|
Client *model.Client4
|
|
BasicUser *model.User
|
|
BasicUser2 *model.User
|
|
TeamAdminUser *model.User
|
|
BasicTeam *model.Team
|
|
BasicChannel *model.Channel
|
|
BasicPrivateChannel *model.Channel
|
|
BasicPrivateChannel2 *model.Channel
|
|
BasicDeletedChannel *model.Channel
|
|
BasicChannel2 *model.Channel
|
|
BasicPost *model.Post
|
|
Group *model.Group
|
|
|
|
SystemAdminClient *model.Client4
|
|
SystemAdminUser *model.User
|
|
tempWorkspace string
|
|
|
|
SystemManagerClient *model.Client4
|
|
SystemManagerUser *model.User
|
|
|
|
LocalClient *model.Client4
|
|
|
|
IncludeCacheLayer bool
|
|
|
|
LogBuffer *mlog.Buffer
|
|
TestLogger *mlog.Logger
|
|
}
|
|
|
|
var mainHelper *testlib.MainHelper
|
|
|
|
func SetMainHelper(mh *testlib.MainHelper) {
|
|
mainHelper = mh
|
|
}
|
|
|
|
func setupTestHelper(tb testing.TB, dbStore store.Store, sqlSettings *model.SqlSettings, searchEngine *searchengine.Broker, enterprise bool, includeCache bool,
|
|
updateConfig func(*model.Config), options []app.Option,
|
|
) *TestHelper {
|
|
tempWorkspace, err := os.MkdirTemp("", "apptest")
|
|
require.NoError(tb, err)
|
|
|
|
memoryStore, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{IgnoreEnvironmentOverrides: true})
|
|
require.NoError(tb, err, "failed to initialize memory store")
|
|
|
|
memoryConfig := &model.Config{
|
|
SqlSettings: model.SafeDereference(sqlSettings),
|
|
}
|
|
memoryConfig.SetDefaults()
|
|
*memoryConfig.ServiceSettings.LicenseFileLocation = filepath.Join(tempWorkspace, "license.json")
|
|
*memoryConfig.FileSettings.Directory = filepath.Join(tempWorkspace, "data")
|
|
*memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
|
*memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
|
*memoryConfig.FileSettings.Directory = filepath.Join(tempWorkspace, "data")
|
|
*memoryConfig.ServiceSettings.EnableLocalMode = true
|
|
*memoryConfig.ServiceSettings.LocalModeSocketLocation = filepath.Join(tempWorkspace, "mattermost_local.sock")
|
|
*memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
|
|
|
|
// Check for environment variable override for console log level (useful for debugging tests)
|
|
consoleLevel := os.Getenv("MM_LOGSETTINGS_CONSOLELEVEL")
|
|
if consoleLevel == "" {
|
|
consoleLevel = mlog.LvlStdLog.Name
|
|
}
|
|
*memoryConfig.LogSettings.ConsoleLevel = consoleLevel
|
|
*memoryConfig.LogSettings.FileLocation = filepath.Join(tempWorkspace, "logs", "mattermost.log")
|
|
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
|
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
|
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
|
// Enabling Redis with Postgres.
|
|
if *memoryConfig.SqlSettings.DriverName == model.DatabaseDriverPostgres && !mainHelper.Options.RunParallel {
|
|
*memoryConfig.CacheSettings.CacheType = model.CacheTypeRedis
|
|
redisHost := "localhost"
|
|
if os.Getenv("IS_CI") == "true" {
|
|
redisHost = "redis"
|
|
}
|
|
*memoryConfig.CacheSettings.RedisAddress = redisHost + ":6379"
|
|
*memoryConfig.CacheSettings.DisableClientCache = true
|
|
*memoryConfig.CacheSettings.RedisDB = 0
|
|
*memoryConfig.CacheSettings.RedisCachePrefix = model.NewId()
|
|
options = append(options, app.ForceEnableRedis())
|
|
}
|
|
if updateConfig != nil {
|
|
updateConfig(memoryConfig)
|
|
}
|
|
memoryStore.Set(memoryConfig)
|
|
for _, signaturePublicKeyFile := range memoryConfig.PluginSettings.SignaturePublicKeyFiles {
|
|
var signaturePublicKey []byte
|
|
signaturePublicKey, err = os.ReadFile(signaturePublicKeyFile)
|
|
require.NoError(tb, err, "failed to read signature public key file %s", signaturePublicKeyFile)
|
|
memoryStore.SetFile(signaturePublicKeyFile, signaturePublicKey)
|
|
}
|
|
|
|
configStore, err := config.NewStoreFromBacking(memoryStore, nil, false)
|
|
require.NoError(tb, err)
|
|
|
|
options = append(options, app.ConfigStore(configStore))
|
|
if includeCache {
|
|
// Adds the cache layer to the test store
|
|
options = append(options, app.StoreOverrideWithCache(dbStore))
|
|
} else {
|
|
options = append(options, app.StoreOverride(dbStore))
|
|
}
|
|
|
|
buffer := &mlog.Buffer{}
|
|
|
|
testLogger, err := mlog.NewLogger()
|
|
require.NoError(tb, err)
|
|
logCfg, err := config.MloggerConfigFromLoggerConfig(&memoryConfig.LogSettings, nil, config.GetLogFileLocation)
|
|
require.NoError(tb, err)
|
|
err = testLogger.ConfigureTargets(logCfg, nil)
|
|
require.NoError(tb, err, "failed to configure test logger")
|
|
err = mlog.AddWriterTarget(testLogger, buffer, true, mlog.StdAll...)
|
|
require.NoError(tb, err, "failed to add writer target to test logger")
|
|
// lock logger config so server init cannot override it during testing.
|
|
testLogger.LockConfiguration()
|
|
options = append(options, app.SetLogger(testLogger))
|
|
|
|
s, err := app.NewServer(options...)
|
|
require.NoError(tb, err)
|
|
|
|
th := &TestHelper{
|
|
App: app.New(app.ServerConnector(s.Channels())),
|
|
Server: s,
|
|
Context: request.EmptyContext(testLogger),
|
|
ConfigStore: configStore,
|
|
IncludeCacheLayer: includeCache,
|
|
TestLogger: testLogger,
|
|
LogBuffer: buffer,
|
|
Store: dbStore,
|
|
}
|
|
|
|
if s.Platform().SearchEngine != nil && s.Platform().SearchEngine.BleveEngine != nil && searchEngine != nil {
|
|
searchEngine.BleveEngine = s.Platform().SearchEngine.BleveEngine
|
|
}
|
|
|
|
if searchEngine != nil {
|
|
th.App.SetSearchEngine(searchEngine)
|
|
}
|
|
|
|
th.App.Srv().SetLicense(getLicense(enterprise, memoryConfig))
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.TeamSettings.MaxUsersPerTeam = 50
|
|
*cfg.RateLimitSettings.Enable = false
|
|
*cfg.EmailSettings.SendEmailNotifications = true
|
|
*cfg.ServiceSettings.SiteURL = ""
|
|
|
|
// Disable sniffing, otherwise elastic client fails to connect to docker node
|
|
// More details: https://github.com/olivere/elastic/wiki/Sniffing
|
|
*cfg.ElasticsearchSettings.Sniff = false
|
|
|
|
*cfg.TeamSettings.EnableOpenServer = true
|
|
|
|
// Disable strict password requirements for test
|
|
*cfg.PasswordSettings.MinimumLength = 5
|
|
*cfg.PasswordSettings.Lowercase = false
|
|
*cfg.PasswordSettings.Uppercase = false
|
|
*cfg.PasswordSettings.Symbol = false
|
|
*cfg.PasswordSettings.Number = false
|
|
|
|
*cfg.ServiceSettings.ListenAddress = "localhost:0"
|
|
})
|
|
|
|
// Support updating feature flags without resorting to os.Setenv which
|
|
// isn't concurrently safe.
|
|
if updateConfig != nil {
|
|
configStore.SetReadOnlyFF(false)
|
|
th.App.UpdateConfig(updateConfig)
|
|
}
|
|
|
|
err = th.Server.Start()
|
|
require.NoError(tb, err)
|
|
|
|
_, err = Init(th.App.Srv())
|
|
require.NoError(tb, err)
|
|
web.New(th.App.Srv())
|
|
wsapi.Init(th.App.Srv())
|
|
|
|
th.Client = th.CreateClient()
|
|
th.SystemAdminClient = th.CreateClient()
|
|
th.SystemManagerClient = th.CreateClient()
|
|
|
|
// Verify handling of the supported true/false values by randomizing on each run.
|
|
trueValues := []string{"1", "t", "T", "TRUE", "true", "True"}
|
|
falseValues := []string{"0", "f", "F", "FALSE", "false", "False"}
|
|
trueString := trueValues[rand.Intn(len(trueValues))]
|
|
falseString := falseValues[rand.Intn(len(falseValues))]
|
|
testLogger.Debug("Configured Client4 bool string values", mlog.String("true", trueString), mlog.String("false", falseString))
|
|
th.Client.SetBoolString(true, trueString)
|
|
th.Client.SetBoolString(false, falseString)
|
|
|
|
th.LocalClient = th.CreateLocalClient(*memoryConfig.ServiceSettings.LocalModeSocketLocation)
|
|
|
|
if th.tempWorkspace == "" {
|
|
th.tempWorkspace = tempWorkspace
|
|
}
|
|
|
|
return th
|
|
}
|
|
|
|
func getLicense(enterprise bool, cfg *model.Config) *model.License {
|
|
if *cfg.ConnectedWorkspacesSettings.EnableRemoteClusterService || *cfg.ConnectedWorkspacesSettings.EnableSharedChannels {
|
|
return model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)
|
|
}
|
|
if enterprise {
|
|
return model.NewTestLicense()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setupStores(tb testing.TB) (store.Store, *model.SqlSettings, *searchengine.Broker) {
|
|
var dbStore store.Store
|
|
var dbSettings *model.SqlSettings
|
|
var searchEngine *searchengine.Broker
|
|
if mainHelper.Options.RunParallel {
|
|
dbStore, _, dbSettings, searchEngine = mainHelper.GetNewStores(tb)
|
|
tb.Cleanup(func() {
|
|
dbStore.Close()
|
|
})
|
|
} else {
|
|
dbStore = mainHelper.GetStore()
|
|
dbStore.DropAllTables()
|
|
dbStore.MarkSystemRanUnitTests()
|
|
mainHelper.PreloadMigrations()
|
|
searchEngine = mainHelper.GetSearchEngine()
|
|
dbSettings = mainHelper.Settings
|
|
}
|
|
|
|
return dbStore, dbSettings, searchEngine
|
|
}
|
|
|
|
func SetupEnterprise(tb testing.TB, options ...app.Option) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
removeSpuriousErrors := func(config *model.Config) {
|
|
// If not set, you will receive an unactionable error in the console
|
|
*config.ServiceSettings.SiteURL = "http://localhost:8065"
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, true, true, removeSpuriousErrors, options)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func Setup(tb testing.TB) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, nil)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func SetupAndApplyConfigBeforeLogin(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, nil)
|
|
th.App.UpdateConfig(updateConfig)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, updateConfig, nil)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, false, false, updateConfig, nil)
|
|
statusMock := mocks.StatusStore{}
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
|
emptyMockStore := mocks.Store{}
|
|
emptyMockStore.On("Close").Return(nil)
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
|
return th
|
|
}
|
|
|
|
func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, false, false, nil, nil)
|
|
statusMock := mocks.StatusStore{}
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
|
emptyMockStore := mocks.Store{}
|
|
emptyMockStore.On("Close").Return(nil)
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
|
return th
|
|
}
|
|
|
|
func SetupEnterpriseWithStoreMock(tb testing.TB, options ...app.Option) *TestHelper {
|
|
removeSpuriousErrors := func(config *model.Config) {
|
|
// If not set, you will receive an unactionable error in the console
|
|
*config.ServiceSettings.SiteURL = "http://localhost:8065"
|
|
}
|
|
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, true, false, removeSpuriousErrors, options)
|
|
statusMock := mocks.StatusStore{}
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
|
emptyMockStore := mocks.Store{}
|
|
emptyMockStore.On("Close").Return(nil)
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
|
return th
|
|
}
|
|
|
|
func SetupWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, options)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func SetupEnterpriseWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
|
|
if testing.Short() {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
if mainHelper == nil {
|
|
tb.SkipNow()
|
|
}
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, true, true, nil, options)
|
|
th.InitLogin(tb)
|
|
|
|
return th
|
|
}
|
|
|
|
func (th *TestHelper) ShutdownApp() {
|
|
done := make(chan bool)
|
|
go func() {
|
|
th.Server.Shutdown()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(30 * time.Second):
|
|
// panic instead of fatal to terminate all tests in this package, otherwise the
|
|
// still running App could spuriously fail subsequent tests.
|
|
panic("failed to shutdown App within 30 seconds")
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) TearDown() {
|
|
if th.IncludeCacheLayer {
|
|
// Clean all the caches
|
|
th.App.Srv().InvalidateAllCaches()
|
|
}
|
|
|
|
th.ShutdownApp()
|
|
|
|
// Cleanup the workspace
|
|
if th.tempWorkspace != "" {
|
|
err := os.RemoveAll(th.tempWorkspace)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func closeBody(r *http.Response) {
|
|
if r.Body != nil {
|
|
_, _ = io.Copy(io.Discard, r.Body)
|
|
_ = r.Body.Close()
|
|
}
|
|
}
|
|
|
|
var (
|
|
initBasicOnce sync.Once
|
|
userCache struct {
|
|
SystemAdminUser *model.User
|
|
SystemManagerUser *model.User
|
|
TeamAdminUser *model.User
|
|
BasicUser *model.User
|
|
BasicUser2 *model.User
|
|
}
|
|
)
|
|
|
|
func (th *TestHelper) InitLogin(tb testing.TB) *TestHelper {
|
|
th.waitForConnectivity(tb)
|
|
|
|
// create users once and cache them because password hashing is slow
|
|
initBasicOnce.Do(func() {
|
|
var err *model.AppError
|
|
|
|
th.SystemAdminUser = th.CreateUser()
|
|
th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
|
|
th.SystemAdminUser, err = th.App.GetUser(th.SystemAdminUser.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
userCache.SystemAdminUser = th.SystemAdminUser.DeepCopy()
|
|
|
|
th.SystemManagerUser = th.CreateUser()
|
|
th.App.UpdateUserRoles(th.Context, th.SystemManagerUser.Id, model.SystemUserRoleId+" "+model.SystemManagerRoleId, false)
|
|
th.SystemManagerUser, err = th.App.GetUser(th.SystemManagerUser.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
userCache.SystemManagerUser = th.SystemManagerUser.DeepCopy()
|
|
|
|
th.TeamAdminUser = th.CreateUser()
|
|
th.App.UpdateUserRoles(th.Context, th.TeamAdminUser.Id, model.SystemUserRoleId, false)
|
|
th.TeamAdminUser, err = th.App.GetUser(th.TeamAdminUser.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
userCache.TeamAdminUser = th.TeamAdminUser.DeepCopy()
|
|
|
|
th.BasicUser = th.CreateUser()
|
|
th.BasicUser, err = th.App.GetUser(th.BasicUser.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
userCache.BasicUser = th.BasicUser.DeepCopy()
|
|
|
|
th.BasicUser2 = th.CreateUser()
|
|
th.BasicUser2, err = th.App.GetUser(th.BasicUser2.Id)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
userCache.BasicUser2 = th.BasicUser2.DeepCopy()
|
|
})
|
|
|
|
// restore cached users
|
|
th.SystemAdminUser = userCache.SystemAdminUser.DeepCopy()
|
|
th.SystemManagerUser = userCache.SystemManagerUser.DeepCopy()
|
|
th.TeamAdminUser = userCache.TeamAdminUser.DeepCopy()
|
|
th.BasicUser = userCache.BasicUser.DeepCopy()
|
|
th.BasicUser2 = userCache.BasicUser2.DeepCopy()
|
|
|
|
th.Store.User().InsertUsers([]*model.User{th.SystemAdminUser, th.TeamAdminUser, th.BasicUser, th.BasicUser2, th.SystemManagerUser})
|
|
|
|
// restore non hashed password for login
|
|
th.SystemAdminUser.Password = "Pa$$word11"
|
|
th.TeamAdminUser.Password = "Pa$$word11"
|
|
th.BasicUser.Password = "Pa$$word11"
|
|
th.BasicUser2.Password = "Pa$$word11"
|
|
th.SystemManagerUser.Password = "Pa$$word11"
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go func() {
|
|
th.LoginSystemAdmin()
|
|
wg.Done()
|
|
}()
|
|
go func() {
|
|
th.LoginTeamAdmin()
|
|
wg.Done()
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
return th
|
|
}
|
|
|
|
func (th *TestHelper) InitBasic() *TestHelper {
|
|
th.BasicTeam = th.CreateTeam()
|
|
th.BasicChannel = th.CreatePublicChannel()
|
|
th.BasicPrivateChannel = th.CreatePrivateChannel()
|
|
th.BasicPrivateChannel2 = th.CreatePrivateChannel()
|
|
th.BasicDeletedChannel = th.CreatePublicChannel()
|
|
th.BasicChannel2 = th.CreatePublicChannel()
|
|
th.BasicPost = th.CreatePost()
|
|
th.LinkUserToTeam(th.BasicUser, th.BasicTeam)
|
|
th.LinkUserToTeam(th.BasicUser2, th.BasicTeam)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicChannel, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicChannel2, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel2, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicPrivateChannel, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicPrivateChannel, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicDeletedChannel, false)
|
|
th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicDeletedChannel, false)
|
|
th.App.UpdateUserRoles(th.Context, th.BasicUser.Id, model.SystemUserRoleId, false)
|
|
th.Client.DeleteChannel(context.Background(), th.BasicDeletedChannel.Id)
|
|
th.LoginBasic()
|
|
th.Group = th.CreateGroup()
|
|
|
|
return th
|
|
}
|
|
|
|
func (th *TestHelper) DeleteBots() *TestHelper {
|
|
preexistingBots, _ := th.App.GetBots(th.Context, &model.BotGetOptions{Page: 0, PerPage: 100})
|
|
for _, bot := range preexistingBots {
|
|
th.App.PermanentDeleteBot(th.Context, bot.UserId)
|
|
}
|
|
return th
|
|
}
|
|
|
|
func (th *TestHelper) waitForConnectivity(tb testing.TB) {
|
|
for i := 0; i < 1000; i++ {
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", th.App.Srv().ListenAddr.Port))
|
|
if err == nil {
|
|
conn.Close()
|
|
return
|
|
}
|
|
time.Sleep(time.Millisecond * 20)
|
|
}
|
|
tb.Fatal("unable to connect")
|
|
}
|
|
|
|
func (th *TestHelper) CreateClient() *model.Client4 {
|
|
return model.NewAPIv4Client(fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port))
|
|
}
|
|
|
|
// ToDo: maybe move this to NewAPIv4SocketClient and reuse it in mmctl
|
|
func (th *TestHelper) CreateLocalClient(socketPath string) *model.Client4 {
|
|
httpClient := &http.Client{
|
|
Transport: &http.Transport{
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
return net.Dial("unix", socketPath)
|
|
},
|
|
},
|
|
}
|
|
|
|
return &model.Client4{
|
|
APIURL: "http://_" + model.APIURLSuffix,
|
|
HTTPClient: httpClient,
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) createConnectedWebSocketClient(t *testing.T, client *model.Client4) *model.WebSocketClient {
|
|
t.Helper()
|
|
wsClient, err := th.CreateWebSocketClientWithClient(client)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, wsClient, "webSocketClient should not be nil")
|
|
wsClient.Listen()
|
|
t.Cleanup(wsClient.Close)
|
|
|
|
// Ensure WS is connected. First event should be hello message.
|
|
select {
|
|
case ev := <-wsClient.EventChannel:
|
|
require.Equal(t, model.WebsocketEventHello, ev.EventType())
|
|
case <-time.After(5 * time.Second):
|
|
require.FailNow(t, "hello event was not received within the timeout period")
|
|
}
|
|
|
|
return wsClient
|
|
}
|
|
|
|
func (th *TestHelper) CreateConnectedWebSocketClient(t *testing.T) *model.WebSocketClient {
|
|
return th.createConnectedWebSocketClient(t, th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) CreateConnectedWebSocketClientWithClient(t *testing.T, client *model.Client4) *model.WebSocketClient {
|
|
return th.createConnectedWebSocketClient(t, client)
|
|
}
|
|
|
|
func (th *TestHelper) CreateWebSocketClient() (*model.WebSocketClient, error) {
|
|
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), th.Client.AuthToken)
|
|
}
|
|
|
|
func (th *TestHelper) CreateReliableWebSocketClient(connID string, seqNo int) (*model.WebSocketClient, error) {
|
|
return model.NewReliableWebSocketClientWithDialer(websocket.DefaultDialer, fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), th.Client.AuthToken, connID, seqNo, true)
|
|
}
|
|
|
|
func (th *TestHelper) CreateWebSocketClientWithClient(client *model.Client4) (*model.WebSocketClient, error) {
|
|
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), client.AuthToken)
|
|
}
|
|
|
|
func (th *TestHelper) CreateBotWithSystemAdminClient() *model.Bot {
|
|
return th.CreateBotWithClient((th.SystemAdminClient))
|
|
}
|
|
|
|
func (th *TestHelper) CreateBotWithClient(client *model.Client4) *model.Bot {
|
|
bot := &model.Bot{
|
|
Username: GenerateTestUsername(),
|
|
DisplayName: "a bot",
|
|
Description: "bot",
|
|
}
|
|
|
|
rbot, _, err := client.CreateBot(context.Background(), bot)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rbot
|
|
}
|
|
|
|
func (th *TestHelper) CreateUser() *model.User {
|
|
return th.CreateUserWithClient(th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) CreateGuestUser(tb testing.TB) *model.User {
|
|
tb.Helper()
|
|
|
|
guestUser := th.CreateUserWithClient(th.Client)
|
|
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, guestUser.Id, model.SystemGuestRoleId, false)
|
|
require.Nil(tb, appErr)
|
|
|
|
return guestUser
|
|
}
|
|
|
|
func (th *TestHelper) CreateTeam() *model.Team {
|
|
return th.CreateTeamWithClient(th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) CreateTeamWithClient(client *model.Client4) *model.Team {
|
|
id := model.NewId()
|
|
team := &model.Team{
|
|
DisplayName: "dn_" + id,
|
|
Name: GenerateTestTeamName(),
|
|
Email: th.GenerateTestEmail(),
|
|
Type: model.TeamOpen,
|
|
}
|
|
|
|
rteam, _, err := client.CreateTeam(context.Background(), team)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rteam
|
|
}
|
|
|
|
func (th *TestHelper) CreateUserWithClient(client *model.Client4) *model.User {
|
|
id := model.NewId()
|
|
|
|
user := &model.User{
|
|
Email: th.GenerateTestEmail(),
|
|
Username: GenerateTestUsername(),
|
|
Nickname: "nn_" + id,
|
|
FirstName: "f_" + id,
|
|
LastName: "l_" + id,
|
|
Password: "Pa$$word11",
|
|
}
|
|
|
|
ruser, _, err := client.CreateUser(context.Background(), user)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
ruser.Password = "Pa$$word11"
|
|
_, err = th.App.Srv().Store().User().VerifyEmail(ruser.Id, ruser.Email)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return ruser
|
|
}
|
|
|
|
func (th *TestHelper) CreateUserWithAuth(authService string) *model.User {
|
|
id := model.NewId()
|
|
user := &model.User{
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
Username: "un_" + id,
|
|
Nickname: "nn_" + id,
|
|
EmailVerified: true,
|
|
AuthService: authService,
|
|
}
|
|
user, err := th.App.CreateUser(th.Context, user)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return user
|
|
}
|
|
|
|
// CreateGuestAndClient creates a guest user, adds them to the basic
|
|
// team, basic channel and basic private channel, and generates an API
|
|
// client ready to use
|
|
func (th *TestHelper) CreateGuestAndClient(tb testing.TB) (*model.User, *model.Client4) {
|
|
tb.Helper()
|
|
id := model.NewId()
|
|
|
|
// create a guest user and add it to the basic team and public/private channels
|
|
guest, cgErr := th.App.CreateGuest(th.Context, &model.User{
|
|
Email: "test_guest" + id + "@sample.com",
|
|
Username: "guest_" + id,
|
|
Nickname: "guest_" + id,
|
|
Password: "Password1",
|
|
EmailVerified: true,
|
|
})
|
|
require.Nil(tb, cgErr)
|
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guest.Id, th.SystemAdminUser.Id)
|
|
require.Nil(tb, appErr)
|
|
th.AddUserToChannel(guest, th.BasicChannel)
|
|
th.AddUserToChannel(guest, th.BasicPrivateChannel)
|
|
|
|
// create a client and login the guest
|
|
guestClient := th.CreateClient()
|
|
_, _, err := guestClient.Login(context.Background(), guest.Email, "Password1")
|
|
require.NoError(tb, err)
|
|
|
|
return guest, guestClient
|
|
}
|
|
|
|
func (th *TestHelper) SetupLdapConfig() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.EnableMultifactorAuthentication = true
|
|
*cfg.LdapSettings.Enable = true
|
|
*cfg.LdapSettings.EnableSync = true
|
|
*cfg.LdapSettings.LdapServer = "dockerhost"
|
|
*cfg.LdapSettings.BaseDN = "dc=mm,dc=test,dc=com"
|
|
*cfg.LdapSettings.BindUsername = "cn=admin,dc=mm,dc=test,dc=com"
|
|
*cfg.LdapSettings.BindPassword = "mostest"
|
|
*cfg.LdapSettings.FirstNameAttribute = "cn"
|
|
*cfg.LdapSettings.LastNameAttribute = "sn"
|
|
*cfg.LdapSettings.NicknameAttribute = "cn"
|
|
*cfg.LdapSettings.EmailAttribute = "mail"
|
|
*cfg.LdapSettings.UsernameAttribute = "uid"
|
|
*cfg.LdapSettings.IdAttribute = "cn"
|
|
*cfg.LdapSettings.LoginIdAttribute = "uid"
|
|
*cfg.LdapSettings.SkipCertificateVerification = true
|
|
*cfg.LdapSettings.GroupFilter = ""
|
|
*cfg.LdapSettings.GroupDisplayNameAttribute = "cN"
|
|
*cfg.LdapSettings.GroupIdAttribute = "entRyUuId"
|
|
*cfg.LdapSettings.MaxPageSize = 0
|
|
})
|
|
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
|
}
|
|
|
|
func (th *TestHelper) SetupSamlConfig() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.SamlSettings.Enable = true
|
|
*cfg.SamlSettings.Verify = false
|
|
*cfg.SamlSettings.Encrypt = false
|
|
*cfg.SamlSettings.IdpURL = "https://does.notmatter.example"
|
|
*cfg.SamlSettings.IdpDescriptorURL = "https://localhost/adfs/services/trust"
|
|
*cfg.SamlSettings.AssertionConsumerServiceURL = "https://localhost/login/sso/saml"
|
|
*cfg.SamlSettings.ServiceProviderIdentifier = "https://localhost/login/sso/saml"
|
|
*cfg.SamlSettings.IdpCertificateFile = app.SamlIdpCertificateName
|
|
*cfg.SamlSettings.PrivateKeyFile = app.SamlPrivateKeyName
|
|
*cfg.SamlSettings.PublicCertificateFile = app.SamlPublicCertificateName
|
|
*cfg.SamlSettings.EmailAttribute = "Email"
|
|
*cfg.SamlSettings.UsernameAttribute = "Username"
|
|
*cfg.SamlSettings.FirstNameAttribute = "FirstName"
|
|
*cfg.SamlSettings.LastNameAttribute = "LastName"
|
|
*cfg.SamlSettings.NicknameAttribute = ""
|
|
*cfg.SamlSettings.PositionAttribute = ""
|
|
*cfg.SamlSettings.LocaleAttribute = ""
|
|
*cfg.SamlSettings.SignatureAlgorithm = model.SamlSettingsSignatureAlgorithmSha256
|
|
*cfg.SamlSettings.CanonicalAlgorithm = model.SamlSettingsCanonicalAlgorithmC14n11
|
|
})
|
|
th.App.Srv().SetLicense(model.NewTestLicense("saml"))
|
|
}
|
|
|
|
func (th *TestHelper) CreatePublicChannel() *model.Channel {
|
|
return th.CreateChannelWithClient(th.Client, model.ChannelTypeOpen)
|
|
}
|
|
|
|
func (th *TestHelper) CreatePrivateChannel() *model.Channel {
|
|
return th.CreateChannelWithClient(th.Client, model.ChannelTypePrivate)
|
|
}
|
|
|
|
func (th *TestHelper) CreateChannelWithClient(client *model.Client4, channelType model.ChannelType) *model.Channel {
|
|
return th.CreateChannelWithClientAndTeam(client, channelType, th.BasicTeam.Id)
|
|
}
|
|
|
|
func (th *TestHelper) CreateChannelWithClientAndTeam(client *model.Client4, channelType model.ChannelType, teamID string) *model.Channel {
|
|
id := model.NewId()
|
|
|
|
channel := &model.Channel{
|
|
DisplayName: "dn_" + id,
|
|
Name: GenerateTestChannelName(),
|
|
Type: channelType,
|
|
TeamId: teamID,
|
|
}
|
|
|
|
rchannel, _, err := client.CreateChannel(context.Background(), channel)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rchannel
|
|
}
|
|
|
|
func (th *TestHelper) CreatePost() *model.Post {
|
|
return th.CreatePostWithClient(th.Client, th.BasicChannel)
|
|
}
|
|
|
|
func (th *TestHelper) CreatePinnedPost() *model.Post {
|
|
return th.CreatePinnedPostWithClient(th.Client, th.BasicChannel)
|
|
}
|
|
|
|
func (th *TestHelper) CreateMessagePost(message string) *model.Post {
|
|
return th.CreateMessagePostWithClient(th.Client, th.BasicChannel, message)
|
|
}
|
|
|
|
func (th *TestHelper) CreatePostWithFiles(files ...*model.FileInfo) *model.Post {
|
|
return th.CreatePostWithFilesWithClient(th.Client, th.BasicChannel, files...)
|
|
}
|
|
|
|
func (th *TestHelper) CreatePostInChannelWithFiles(channel *model.Channel, files ...*model.FileInfo) *model.Post {
|
|
return th.CreatePostWithFilesWithClient(th.Client, channel, files...)
|
|
}
|
|
|
|
func (th *TestHelper) CreatePostWithFilesWithClient(client *model.Client4, channel *model.Channel, files ...*model.FileInfo) *model.Post {
|
|
var fileIds model.StringArray
|
|
for i := range files {
|
|
fileIds = append(fileIds, files[i].Id)
|
|
}
|
|
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "message_" + model.NewId(),
|
|
FileIds: fileIds,
|
|
}
|
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rpost
|
|
}
|
|
|
|
func (th *TestHelper) CreatePostWithClient(client *model.Client4, channel *model.Channel) *model.Post {
|
|
id := model.NewId()
|
|
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "message_" + id,
|
|
}
|
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rpost
|
|
}
|
|
|
|
func (th *TestHelper) CreatePinnedPostWithClient(client *model.Client4, channel *model.Channel) *model.Post {
|
|
id := model.NewId()
|
|
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: "message_" + id,
|
|
IsPinned: true,
|
|
}
|
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rpost
|
|
}
|
|
|
|
func (th *TestHelper) CreateMessagePostWithClient(client *model.Client4, channel *model.Channel, message string) *model.Post {
|
|
post := &model.Post{
|
|
ChannelId: channel.Id,
|
|
Message: message,
|
|
}
|
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return rpost
|
|
}
|
|
|
|
func (th *TestHelper) CreateMessagePostNoClient(channel *model.Channel, message string, createAtTime int64) *model.Post {
|
|
post, err := th.App.Srv().Store().Post().Save(th.Context, &model.Post{
|
|
UserId: th.BasicUser.Id,
|
|
ChannelId: channel.Id,
|
|
Message: message,
|
|
CreateAt: createAtTime,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return post
|
|
}
|
|
|
|
func (th *TestHelper) CreateDmChannel(user *model.User) *model.Channel {
|
|
var err *model.AppError
|
|
var channel *model.Channel
|
|
if channel, err = th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id); err != nil {
|
|
panic(err)
|
|
}
|
|
return channel
|
|
}
|
|
|
|
func (th *TestHelper) PatchChannelModerationsForMembers(channelId, name string, val bool) {
|
|
patch := []*model.ChannelModerationPatch{{
|
|
Name: &name,
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(val)},
|
|
}}
|
|
|
|
channel, err := th.App.GetChannel(th.Context, channelId)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
_, err = th.App.PatchChannelModerationsForChannel(th.Context, channel, patch)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LoginBasic() {
|
|
th.LoginBasicWithClient(th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) LoginBasic2() {
|
|
th.LoginBasic2WithClient(th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) LoginTeamAdmin() {
|
|
th.LoginTeamAdminWithClient(th.Client)
|
|
}
|
|
|
|
func (th *TestHelper) LoginSystemAdmin() {
|
|
th.LoginSystemAdminWithClient(th.SystemAdminClient)
|
|
}
|
|
|
|
func (th *TestHelper) LoginSystemManager() {
|
|
th.LoginSystemManagerWithClient(th.SystemManagerClient)
|
|
}
|
|
|
|
func (th *TestHelper) LoginBasicWithClient(client *model.Client4) {
|
|
_, _, err := client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LoginBasic2WithClient(client *model.Client4) {
|
|
_, _, err := client.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LoginTeamAdminWithClient(client *model.Client4) {
|
|
_, _, err := client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LoginSystemManagerWithClient(client *model.Client4) {
|
|
_, _, err := client.Login(context.Background(), th.SystemManagerUser.Email, th.SystemManagerUser.Password)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LoginSystemAdminWithClient(client *model.Client4) {
|
|
_, _, err := client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) UpdateActiveUser(user *model.User, active bool) {
|
|
_, err := th.App.UpdateActive(th.Context, user, active)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) {
|
|
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) UnlinkUserFromTeam(user *model.User, team *model.Team) {
|
|
err := th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, "")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) AddUserToChannel(user *model.User, channel *model.Channel) *model.ChannelMember {
|
|
member, err := th.App.AddUserToChannel(th.Context, user, channel, false)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return member
|
|
}
|
|
|
|
func (th *TestHelper) RemoveUserFromChannel(user *model.User, channel *model.Channel) {
|
|
err := th.App.RemoveUserFromChannel(th.Context, user.Id, "", channel)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) GenerateTestEmail() string {
|
|
if *th.App.Config().EmailSettings.SMTPServer != "localhost" && os.Getenv("CI_INBUCKET_PORT") == "" {
|
|
return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com")
|
|
}
|
|
return strings.ToLower(model.NewId() + "@localhost")
|
|
}
|
|
|
|
func (th *TestHelper) CreateGroup() *model.Group {
|
|
id := model.NewId()
|
|
group := &model.Group{
|
|
Name: model.NewPointer("n-" + id),
|
|
DisplayName: "dn_" + id,
|
|
Source: model.GroupSourceLdap,
|
|
RemoteId: model.NewPointer("ri_" + model.NewId()),
|
|
}
|
|
|
|
group, err := th.App.CreateGroup(group)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return group
|
|
}
|
|
|
|
// TestForSystemAdminAndLocal runs a test function for both
|
|
// SystemAdmin and Local clients. Several endpoints work in the same
|
|
// way when used by a fully privileged user and through the local
|
|
// mode, so this helper facilitates checking both
|
|
func (th *TestHelper) TestForSystemAdminAndLocal(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
|
var testName string
|
|
if len(name) > 0 {
|
|
testName = name[0] + "/"
|
|
}
|
|
|
|
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
|
f(t, th.SystemAdminClient)
|
|
})
|
|
|
|
t.Run(testName+"LocalClient", func(t *testing.T) {
|
|
f(t, th.LocalClient)
|
|
})
|
|
}
|
|
|
|
// TestForAllClients runs a test function for all the clients
|
|
// registered in the TestHelper
|
|
func (th *TestHelper) TestForAllClients(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
|
var testName string
|
|
if len(name) > 0 {
|
|
testName = name[0] + "/"
|
|
}
|
|
|
|
t.Run(testName+"Client", func(t *testing.T) {
|
|
f(t, th.Client)
|
|
})
|
|
|
|
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
|
f(t, th.SystemAdminClient)
|
|
})
|
|
|
|
t.Run(testName+"LocalClient", func(t *testing.T) {
|
|
f(t, th.LocalClient)
|
|
})
|
|
}
|
|
|
|
func GenerateTestUsername() string {
|
|
return "fakeuser" + model.NewRandomString(10)
|
|
}
|
|
|
|
func GenerateTestTeamName() string {
|
|
return "faketeam" + model.NewRandomString(6)
|
|
}
|
|
|
|
func GenerateTestChannelName() string {
|
|
return "fakechannel" + model.NewRandomString(10)
|
|
}
|
|
|
|
func GenerateTestAppName() string {
|
|
return "fakeoauthapp" + model.NewRandomString(10)
|
|
}
|
|
|
|
func GenerateTestID() string {
|
|
return model.NewId()
|
|
}
|
|
|
|
func CheckUserSanitization(tb testing.TB, user *model.User) {
|
|
tb.Helper()
|
|
|
|
require.Equal(tb, "", user.Password, "password wasn't blank")
|
|
require.Empty(tb, user.AuthData, "auth data wasn't blank")
|
|
require.Equal(tb, "", user.MfaSecret, "mfa secret wasn't blank")
|
|
}
|
|
|
|
func CheckEtag(tb testing.TB, data any, resp *model.Response) {
|
|
tb.Helper()
|
|
|
|
require.Empty(tb, data)
|
|
require.Equal(tb, http.StatusNotModified, resp.StatusCode, "wrong status code for etag")
|
|
}
|
|
|
|
func checkHTTPStatus(tb testing.TB, resp *model.Response, expectedStatus int) {
|
|
tb.Helper()
|
|
|
|
require.NotNilf(tb, resp, "Unexpected nil response, expected http status:%v", expectedStatus)
|
|
require.Equalf(tb, expectedStatus, resp.StatusCode, "Expected http status:%v, got %v", expectedStatus, resp.StatusCode)
|
|
}
|
|
|
|
func CheckOKStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusOK)
|
|
}
|
|
|
|
func CheckCreatedStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusCreated)
|
|
}
|
|
|
|
func CheckNoContentStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusNoContent)
|
|
}
|
|
|
|
func CheckForbiddenStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusForbidden)
|
|
}
|
|
|
|
func CheckUnauthorizedStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusUnauthorized)
|
|
}
|
|
|
|
func CheckNotFoundStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusNotFound)
|
|
}
|
|
|
|
func CheckBadRequestStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusBadRequest)
|
|
}
|
|
|
|
func CheckUnprocessableEntityStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusUnprocessableEntity)
|
|
}
|
|
|
|
func CheckNotImplementedStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusNotImplemented)
|
|
}
|
|
|
|
func CheckRequestEntityTooLargeStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusRequestEntityTooLarge)
|
|
}
|
|
|
|
func CheckInternalErrorStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusInternalServerError)
|
|
}
|
|
|
|
func CheckServiceUnavailableStatus(tb testing.TB, resp *model.Response) {
|
|
tb.Helper()
|
|
checkHTTPStatus(tb, resp, http.StatusServiceUnavailable)
|
|
}
|
|
|
|
func CheckErrorID(tb testing.TB, err error, errorId string) {
|
|
tb.Helper()
|
|
|
|
require.Error(tb, err, "should have errored with id: %s", errorId)
|
|
|
|
var appError *model.AppError
|
|
ok := errors.As(err, &appError)
|
|
require.True(tb, ok, "should have been a model.AppError")
|
|
|
|
require.Equalf(tb, errorId, appError.Id, "incorrect error id, actual: %s, expected: %s", appError.Id, errorId)
|
|
}
|
|
|
|
func CheckErrorMessage(tb testing.TB, err error, message string) {
|
|
tb.Helper()
|
|
|
|
require.Error(tb, err, "should have errored with message: %s", message)
|
|
|
|
var appError *model.AppError
|
|
ok := errors.As(err, &appError)
|
|
require.True(tb, ok, "should have been a model.AppError")
|
|
|
|
require.Equalf(tb, message, appError.Message, "incorrect error message, actual: %s, expected: %s", appError.Id, message)
|
|
}
|
|
|
|
// Similar to s3.New() but allows initialization of signature v2 or signature v4 client.
|
|
// If signV2 input is false, function always returns signature v4.
|
|
//
|
|
// Additionally this function also takes a user defined region, if set
|
|
// disables automatic region lookup.
|
|
func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool, region string) (*s3.Client, error) {
|
|
var creds *credentials.Credentials
|
|
if signV2 {
|
|
creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV2)
|
|
} else {
|
|
creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV4)
|
|
}
|
|
|
|
opts := s3.Options{
|
|
Creds: creds,
|
|
Secure: secure,
|
|
Region: region,
|
|
}
|
|
return s3.New(endpoint, &opts)
|
|
}
|
|
|
|
func (th *TestHelper) cleanupTestFile(info *model.FileInfo) error {
|
|
cfg := th.App.Config()
|
|
if *cfg.FileSettings.DriverName == model.ImageDriverS3 {
|
|
endpoint := *cfg.FileSettings.AmazonS3Endpoint
|
|
accessKey := *cfg.FileSettings.AmazonS3AccessKeyId
|
|
secretKey := *cfg.FileSettings.AmazonS3SecretAccessKey
|
|
secure := *cfg.FileSettings.AmazonS3SSL
|
|
signV2 := *cfg.FileSettings.AmazonS3SignV2
|
|
region := *cfg.FileSettings.AmazonS3Region
|
|
s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2, region)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bucket := *cfg.FileSettings.AmazonS3Bucket
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.Path, s3.RemoveObjectOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.ThumbnailPath != "" {
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.ThumbnailPath, s3.RemoveObjectOptions{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if info.PreviewPath != "" {
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.PreviewPath, s3.RemoveObjectOptions{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if *cfg.FileSettings.DriverName == model.ImageDriverLocal {
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.Path); err != nil {
|
|
return err
|
|
}
|
|
|
|
if info.ThumbnailPath != "" {
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.ThumbnailPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if info.PreviewPath != "" {
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.PreviewPath); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (th *TestHelper) MakeUserChannelAdmin(user *model.User, channel *model.Channel) {
|
|
if cm, err := th.App.Srv().Store().Channel().GetMember(context.Background(), channel.Id, user.Id); err == nil {
|
|
cm.SchemeAdmin = true
|
|
if _, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, cm); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) UpdateUserToTeamAdmin(user *model.User, team *model.Team) {
|
|
if tm, err := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, user.Id); err == nil {
|
|
tm.SchemeAdmin = true
|
|
if _, err = th.App.Srv().Store().Team().UpdateMember(th.Context, tm); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) UpdateUserToNonTeamAdmin(user *model.User, team *model.Team) {
|
|
if tm, err := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, user.Id); err == nil {
|
|
tm.SchemeAdmin = false
|
|
if _, err = th.App.Srv().Store().Team().UpdateMember(th.Context, tm); err != nil {
|
|
panic(err)
|
|
}
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
|
|
results := make(map[string][]string)
|
|
|
|
for _, roleName := range []string{
|
|
"system_user",
|
|
"system_admin",
|
|
"team_user",
|
|
"team_admin",
|
|
"channel_user",
|
|
"channel_admin",
|
|
} {
|
|
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
|
|
results[roleName] = role.Permissions
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
|
|
for roleName, permissions := range data {
|
|
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
|
|
if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") {
|
|
continue
|
|
}
|
|
|
|
role.Permissions = permissions
|
|
|
|
_, err2 := th.App.UpdateRole(role)
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
|
|
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
|
|
var newPermissions []string
|
|
for _, p := range role.Permissions {
|
|
if p != permission {
|
|
newPermissions = append(newPermissions, p)
|
|
}
|
|
}
|
|
|
|
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
|
return
|
|
}
|
|
|
|
role.Permissions = newPermissions
|
|
|
|
_, err2 := th.App.UpdateRole(role)
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
|
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
|
if err1 != nil {
|
|
panic(err1)
|
|
}
|
|
|
|
for _, existingPermission := range role.Permissions {
|
|
if existingPermission == permission {
|
|
return
|
|
}
|
|
}
|
|
|
|
role.Permissions = append(role.Permissions, permission)
|
|
|
|
_, err2 := th.App.UpdateRole(role)
|
|
if err2 != nil {
|
|
panic(err2)
|
|
}
|
|
}
|
|
|
|
func (th *TestHelper) SetupTeamScheme() *model.Scheme {
|
|
return th.SetupScheme(model.SchemeScopeTeam)
|
|
}
|
|
|
|
func (th *TestHelper) SetupChannelScheme() *model.Scheme {
|
|
return th.SetupScheme(model.SchemeScopeChannel)
|
|
}
|
|
|
|
func (th *TestHelper) SetupScheme(scope string) *model.Scheme {
|
|
scheme, err := th.App.CreateScheme(&model.Scheme{
|
|
Name: model.NewId(),
|
|
DisplayName: model.NewId(),
|
|
Scope: scope,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return scheme
|
|
}
|
|
|
|
func (th *TestHelper) Parallel(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
}
|