mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-15 05:57:37 -04:00
* ci: enable fullyparallel mode for server tests Replace os.Setenv, os.Chdir, and global state mutations with parallel-safe alternatives (t.Setenv, t.Chdir, test hooks) across 37 files. Refactor GetLogRootPath and MM_INSTALL_TYPE to use package-level test hooks instead of environment variables. This enables gotestsum --fullparallel, allowing all test packages to run with maximum parallelism within each shard. Co-authored-by: Claude <claude@anthropic.com> * ci: split fullyparallel from continue-on-error in workflow template - Add new boolean input 'allow-failure' separate from 'fullyparallel' - Change continue-on-error to use allow-failure instead of fullyparallel - Update server-ci.yml to pass allow-failure: true for test coverage job - Allows independent control of parallel execution and failure tolerance Co-authored-by: Claude <claude@anthropic.com> * fix: protect TestOverrideLogRootPath with sync.Mutex for parallel tests - Replace global var TestOverrideLogRootPath with mutex-protected functions - Add SetTestOverrideLogRootPath() and getTestOverrideLogRootPath() functions - Update GetLogRootPath() to use thread-safe getter - Update all test files to use SetTestOverrideLogRootPath() with t.Cleanup() - Fixes race condition when running tests with t.Parallel() Co-authored-by: Claude <claude@anthropic.com> * fix: configure audit settings before server setup in tests - Move ExperimentalAuditSettings from UpdateConfig() to config defaults - Pass audit config via app.Config() option in SetupWithServerOptions() - Fixes audit test setup ordering to configure BEFORE server initialization - Resolves CodeRabbit's audit config timing issue in api4 tests Co-authored-by: Claude <claude@anthropic.com> * fix: implement SetTestOverrideLogRootPath mutex in logger.go The previous commit updated test callers to use SetTestOverrideLogRootPath() but didn't actually create the function in config/logger.go, causing build failures across all CI shards. This commit: - Replaces the exported var TestOverrideLogRootPath with mutex-protected unexported state (testOverrideLogRootPath + testOverrideLogRootMu) - Adds exported SetTestOverrideLogRootPath() setter - Adds unexported getTestOverrideLogRootPath() getter - Updates GetLogRootPath() to use the thread-safe getter - Fixes log_test.go callers that were missed in the previous commit Co-authored-by: Claude <claude@anthropic.com> * fix(test): use SetupConfig for access_control feature flag registration InitAccessControlPolicy() checks FeatureFlags.AttributeBasedAccessControl at route registration time during server startup. Setting the flag via UpdateConfig after Setup() is too late — routes are never registered and API calls return 404. Use SetupConfig() to pass the feature flag in the initial config before server startup, ensuring routes are properly registered. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore BurnOnRead flag state in TestRevealPost subtest The 'feature not enabled' subtest disables BurnOnRead without restoring it via t.Cleanup. Subsequent subtests inherit the disabled state, which can cause 501 errors when they expect the feature to be available. Add t.Cleanup to restore FeatureFlags.BurnOnRead = true after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore EnableSharedChannelsMemberSync flag via t.Cleanup The test disables EnableSharedChannelsMemberSync without restoring it. If the subtest exits early (e.g., require failure), later sibling subtests inherit a disabled flag and become flaky. Add t.Cleanup to restore the flag after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * Fix test parallelism: use instance-scoped overrides and init-time audit config Replace package-level test globals (TestOverrideInstallType, SetTestOverrideLogRootPath) with fields on PlatformService so each test gets its own instance without process-wide mutation. Fix three audit tests (TestUserLoginAudit, TestLogoutAuditAuthStatus, TestUpdatePasswordAudit) that configured the audit logger after server init — the audit logger only reads config at startup, so pass audit settings via app.Config() at init time instead. Also revert the Go 1.24.13 downgrade and bump mattermost-govet to v2.0.2 for Go 1.25.8 compatibility. * Fix audit unit tests * Fix MMCLOUDURL unit tests * Fixed unit tests using MM_NOTIFY_ADMIN_COOL_OFF_DAYS * Make app migrations idempotent for parallel test safety Change System().Save() to System().SaveOrUpdate() in all migration completion markers. When two parallel tests share a database pool entry, both may race through the check-then-insert migration pattern. Save() causes a duplicate key fatal crash; SaveOrUpdate() makes the second write a harmless no-op. * test: address review feedback on fullyparallel PR - Use SetLogRootPathOverride() setter instead of direct field access in platform/support_packet_test.go and platform/log_test.go (pvev) - Restore TestGetLogRootPath in config/logger_test.go to keep MM_LOG_PATH env var coverage; test uses t.Setenv so it runs serially which is fine (pvev) - Fix misleading comment in config_test.go: code uses t.Setenv, not os.Setenv (jgheithcock) Co-authored-by: Claude <claude@anthropic.com> * fix: add missing os import in post_test.go The os import was dropped during a merge conflict resolution while burn-on-read shared channel tests from master still use os.Setenv. Co-authored-by: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: wiggin77 <wiggin77@warpmail.net> Co-authored-by: Mattermost Build <build@mattermost.com>
1053 lines
39 KiB
Go
1053 lines
39 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
"github.com/mattermost/mattermost/server/v8/config"
|
|
)
|
|
|
|
func TestGetConfig(t *testing.T) {
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
_, resp, err := client.GetConfig(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
t.Run("Get config for system admin client", func(t *testing.T) {
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, "", cfg.TeamSettings.SiteName)
|
|
|
|
if *cfg.LdapSettings.BindPassword != model.FakeSetting && *cfg.LdapSettings.BindPassword != "" {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
require.Equal(t, model.FakeSetting, *cfg.FileSettings.PublicLinkSalt, "did not sanitize properly")
|
|
|
|
if *cfg.FileSettings.AmazonS3SecretAccessKey != model.FakeSetting && *cfg.FileSettings.AmazonS3SecretAccessKey != "" {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
if *cfg.EmailSettings.SMTPPassword != model.FakeSetting && *cfg.EmailSettings.SMTPPassword != "" {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
if *cfg.GitLabSettings.Secret != model.FakeSetting && *cfg.GitLabSettings.Secret != "" {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
require.Equal(t, model.FakeSetting, *cfg.SqlSettings.DataSource, "did not sanitize properly")
|
|
require.Equal(t, model.FakeSetting, *cfg.SqlSettings.AtRestEncryptKey, "did not sanitize properly")
|
|
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceReplicas, " "), model.FakeSetting) && len(cfg.SqlSettings.DataSourceReplicas) != 0 {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
if !strings.Contains(strings.Join(cfg.SqlSettings.DataSourceSearchReplicas, " "), model.FakeSetting) && len(cfg.SqlSettings.DataSourceSearchReplicas) != 0 {
|
|
require.FailNow(t, "did not sanitize properly")
|
|
}
|
|
})
|
|
|
|
t.Run("Get config for local client", func(t *testing.T) {
|
|
cfg, _, err := th.LocalClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, model.FakeSetting, *cfg.SqlSettings.DataSource)
|
|
require.NotEqual(t, model.FakeSetting, *cfg.FileSettings.PublicLinkSalt)
|
|
})
|
|
}
|
|
|
|
func TestGetConfigWithAccessTag(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
// set some values so that we know they're not blank
|
|
mockVaryByHeader := model.NewId()
|
|
mockSupportEmail := model.NewId() + "@mattermost.com"
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.RateLimitSettings.VaryByHeader = mockVaryByHeader
|
|
cfg.SupportSettings.SupportEmail = &mockSupportEmail
|
|
})
|
|
|
|
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Username, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
|
|
// add read sysconsole environment config
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleReadEnvironmentRateLimiting.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleReadEnvironmentRateLimiting.Id, model.SystemUserRoleId)
|
|
|
|
cfg, _, err := th.Client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Cannot read value without permission", func(t *testing.T) {
|
|
assert.Nil(t, cfg.SupportSettings.SupportEmail)
|
|
})
|
|
|
|
t.Run("Can read value with permission", func(t *testing.T) {
|
|
assert.Equal(t, mockVaryByHeader, cfg.RateLimitSettings.VaryByHeader)
|
|
})
|
|
|
|
t.Run("Contains Feature Flags", func(t *testing.T) {
|
|
assert.NotNil(t, cfg.FeatureFlags)
|
|
})
|
|
}
|
|
|
|
func TestGetConfigAnyFlagsAccess(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Username, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
_, resp, _ := th.Client.GetConfig(context.Background())
|
|
|
|
t.Run("Check permissions error with no sysconsole read permission", func(t *testing.T) {
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
// add read sysconsole environment config
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleReadEnvironmentRateLimiting.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleReadEnvironmentRateLimiting.Id, model.SystemUserRoleId)
|
|
|
|
cfg, _, err := th.Client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
t.Run("Can read value with permission", func(t *testing.T) {
|
|
assert.NotNil(t, cfg.FeatureFlags)
|
|
assert.NotNil(t, cfg.ExperimentalSettings.RestrictSystemAdmin)
|
|
})
|
|
}
|
|
|
|
func TestReloadConfig(t *testing.T) {
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.ReloadConfig(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
_, err := client.ReloadConfig(context.Background())
|
|
require.NoError(t, err)
|
|
}, "as system admin and local mode")
|
|
|
|
t.Run("as restricted system admin", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
resp, err := client.ReloadConfig(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestUpdateConfig(t *testing.T) {
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := client.UpdateConfig(context.Background(), cfg)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
SiteName := th.App.Config().TeamSettings.SiteName
|
|
|
|
*cfg.TeamSettings.SiteName = "MyFancyName"
|
|
cfg, _, err = client.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "MyFancyName", *cfg.TeamSettings.SiteName, "It should update the SiteName")
|
|
|
|
//Revert the change
|
|
cfg.TeamSettings.SiteName = SiteName
|
|
cfg, _, err = client.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, SiteName, cfg.TeamSettings.SiteName, "It should update the SiteName")
|
|
|
|
t.Run("Should set defaults for missing fields", func(t *testing.T) {
|
|
_, err = th.SystemAdminClient.DoAPIPut(context.Background(), "/config", "{}")
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("Should fail with validation error if invalid config setting is passed", func(t *testing.T) {
|
|
//Revert the change
|
|
badcfg := cfg.Clone()
|
|
badcfg.PasswordSettings.MinimumLength = model.NewPointer(4)
|
|
badcfg.PasswordSettings.MinimumLength = model.NewPointer(4)
|
|
_, resp, err = client.UpdateConfig(context.Background(), badcfg)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "model.config.is_valid.password_length.app_error")
|
|
})
|
|
})
|
|
|
|
t.Run("Ensure PluginSettings.EnableUploads settings are protected", func(t *testing.T) {
|
|
t.Run("sysadmin", func(t *testing.T) {
|
|
oldEnableUploads := *th.App.Config().PluginSettings.EnableUploads
|
|
*cfg.PluginSettings.EnableUploads = !oldEnableUploads
|
|
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
|
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
|
|
|
cfg.PluginSettings.EnableUploads = nil
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
|
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
|
})
|
|
|
|
t.Run("local mode", func(t *testing.T) {
|
|
oldEnableUploads := *th.App.Config().PluginSettings.EnableUploads
|
|
*cfg.PluginSettings.EnableUploads = !oldEnableUploads
|
|
|
|
cfg, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
|
assert.NotEqual(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
|
|
|
cfg.PluginSettings.EnableUploads = nil
|
|
cfg, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldEnableUploads, *cfg.PluginSettings.EnableUploads)
|
|
assert.Equal(t, oldEnableUploads, *th.App.Config().PluginSettings.EnableUploads)
|
|
})
|
|
})
|
|
|
|
t.Run("Should not be able to modify PluginSettings.SignaturePublicKeyFiles", func(t *testing.T) {
|
|
t.Run("sysadmin", func(t *testing.T) {
|
|
oldPublicKeys := th.App.Config().PluginSettings.SignaturePublicKeyFiles
|
|
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, "new_signature")
|
|
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
|
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
|
|
|
cfg.PluginSettings.SignaturePublicKeyFiles = nil
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
|
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
|
})
|
|
|
|
t.Run("local mode", func(t *testing.T) {
|
|
oldPublicKeys := th.App.Config().PluginSettings.SignaturePublicKeyFiles
|
|
cfg.PluginSettings.SignaturePublicKeyFiles = append(cfg.PluginSettings.SignaturePublicKeyFiles, "new_signature")
|
|
|
|
cfg, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
|
assert.NotEqual(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
|
|
|
cfg.PluginSettings.SignaturePublicKeyFiles = nil
|
|
cfg, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldPublicKeys, cfg.PluginSettings.SignaturePublicKeyFiles)
|
|
assert.Equal(t, oldPublicKeys, th.App.Config().PluginSettings.SignaturePublicKeyFiles)
|
|
})
|
|
})
|
|
|
|
t.Run("Should not be able to modify PluginSettings.MarketplaceURL if EnableUploads is disabled", func(t *testing.T) {
|
|
oldURL := "hello.com"
|
|
newURL := "new.com"
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableUploads = false
|
|
*cfg.PluginSettings.MarketplaceURL = oldURL
|
|
})
|
|
|
|
cfg2 := th.App.Config().Clone()
|
|
*cfg2.PluginSettings.MarketplaceURL = newURL
|
|
|
|
cfg2, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldURL, *cfg2.PluginSettings.MarketplaceURL)
|
|
|
|
// Allowing uploads
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.MarketplaceURL = oldURL
|
|
})
|
|
|
|
cfg2 = th.App.Config().Clone()
|
|
*cfg2.PluginSettings.MarketplaceURL = newURL
|
|
|
|
cfg2, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, newURL, *cfg2.PluginSettings.MarketplaceURL)
|
|
})
|
|
|
|
t.Run("Should not be able to modify ComplianceSettings.Directory in cloud", func(t *testing.T) {
|
|
th.App.Srv().SetLicense(model.NewTestLicense("cloud"))
|
|
defer func() {
|
|
appErr := th.App.Srv().RemoveLicense()
|
|
require.Nil(t, appErr)
|
|
}()
|
|
|
|
cfg2 := th.App.Config().Clone()
|
|
*cfg2.ComplianceSettings.Directory = "hellodir"
|
|
|
|
_, resp, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg2)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Should not be able to modify ImportSettings.Directory", func(t *testing.T) {
|
|
t.Run("sysadmin", func(t *testing.T) {
|
|
oldDirectory := *th.App.Config().ImportSettings.Directory
|
|
cfg2 := th.App.Config().Clone()
|
|
*cfg2.ImportSettings.Directory = "./new-import-dir"
|
|
|
|
cfg2, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldDirectory, *cfg2.ImportSettings.Directory)
|
|
assert.Equal(t, oldDirectory, *th.App.Config().ImportSettings.Directory)
|
|
|
|
cfg2.ImportSettings.Directory = nil
|
|
cfg2, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldDirectory, *cfg2.ImportSettings.Directory)
|
|
assert.Equal(t, oldDirectory, *th.App.Config().ImportSettings.Directory)
|
|
})
|
|
|
|
t.Run("local mode", func(t *testing.T) {
|
|
oldDirectory := *th.App.Config().ImportSettings.Directory
|
|
cfg2 := th.App.Config().Clone()
|
|
newDirectory := "./new-import-dir"
|
|
*cfg2.ImportSettings.Directory = newDirectory
|
|
|
|
cfg2, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, newDirectory, *cfg2.ImportSettings.Directory)
|
|
assert.Equal(t, newDirectory, *th.App.Config().ImportSettings.Directory)
|
|
|
|
cfg2.ImportSettings.Directory = nil
|
|
cfg2, _, err = th.LocalClient.UpdateConfig(context.Background(), cfg2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, oldDirectory, *cfg2.ImportSettings.Directory)
|
|
assert.Equal(t, oldDirectory, *th.App.Config().ImportSettings.Directory)
|
|
})
|
|
})
|
|
|
|
t.Run("System Admin should not be able to clear Site URL", func(t *testing.T) {
|
|
siteURL := cfg.ServiceSettings.SiteURL
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SiteURL = siteURL })
|
|
|
|
nonEmptyURL := "http://localhost"
|
|
cfg.ServiceSettings.SiteURL = &nonEmptyURL
|
|
|
|
// Set the SiteURL
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
require.Equal(t, nonEmptyURL, *cfg.ServiceSettings.SiteURL)
|
|
|
|
// Check that the Site URL can't be cleared
|
|
cfg.ServiceSettings.SiteURL = model.NewPointer("")
|
|
cfg, resp, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "api.config.update_config.clear_siteurl.app_error")
|
|
// Check that the Site URL wasn't cleared
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
require.Equal(t, nonEmptyURL, *cfg.ServiceSettings.SiteURL)
|
|
})
|
|
}
|
|
|
|
func TestGetConfigWithoutManageSystemPermission(t *testing.T) {
|
|
th := Setup(t)
|
|
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Username, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("any sysconsole read permission provides config read access", func(t *testing.T) {
|
|
// forbidden by default
|
|
_, resp, err := th.Client.GetConfig(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
// add any sysconsole read permission
|
|
th.AddPermissionToRole(t, model.SysconsoleReadPermissions[0].Id, model.SystemUserRoleId)
|
|
_, _, err = th.Client.GetConfig(context.Background())
|
|
// should be readable now
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestUpdateConfigWithoutManageSystemPermission(t *testing.T) {
|
|
th := Setup(t)
|
|
_, _, err := th.Client.Login(context.Background(), th.BasicUser.Username, th.BasicUser.Password)
|
|
require.NoError(t, err)
|
|
|
|
// add read sysconsole integrations config
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleReadIntegrationsIntegrationManagement.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleReadIntegrationsIntegrationManagement.Id, model.SystemUserRoleId)
|
|
|
|
t.Run("sysconsole read permission does not provides config write access", func(t *testing.T) {
|
|
// should be readable because has a sysconsole read permission
|
|
cfg, _, err := th.Client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := th.Client.UpdateConfig(context.Background(), cfg)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("the wrong write permission does not grant access", func(t *testing.T) {
|
|
// should be readable because has a sysconsole read permission
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
originalValue := *cfg.ServiceSettings.AllowCorsFrom
|
|
|
|
// add the wrong write permission
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleWriteAboutEditionAndLicense.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleWriteAboutEditionAndLicense.Id, model.SystemUserRoleId)
|
|
|
|
// try update a config value allowed by sysconsole WRITE integrations
|
|
mockVal := model.NewId()
|
|
cfg.ServiceSettings.AllowCorsFrom = &mockVal
|
|
_, _, err = th.Client.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
// ensure the config setting was not updated
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, *cfg.ServiceSettings.AllowCorsFrom, originalValue)
|
|
})
|
|
|
|
t.Run("config value is writeable by specific system console permission", func(t *testing.T) {
|
|
// should be readable because has a sysconsole read permission
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleWriteIntegrationsCors.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleWriteIntegrationsCors.Id, model.SystemUserRoleId)
|
|
th.AddPermissionToRole(t, model.PermissionSysconsoleReadIntegrationsCors.Id, model.SystemUserRoleId)
|
|
defer th.RemovePermissionFromRole(t, model.PermissionSysconsoleReadIntegrationsCors.Id, model.SystemUserRoleId)
|
|
|
|
// try update a config value allowed by sysconsole WRITE integrations
|
|
mockVal := model.NewId()
|
|
cfg.ServiceSettings.AllowCorsFrom = &mockVal
|
|
_, _, err = th.Client.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
// ensure the config setting was updated
|
|
cfg, _, err = th.Client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, *cfg.ServiceSettings.AllowCorsFrom, mockVal)
|
|
})
|
|
}
|
|
|
|
func TestUpdateConfigMessageExportSpecialHandling(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
messageExportEnabled := *th.App.Config().MessageExportSettings.EnableExport
|
|
messageExportTimestamp := *th.App.Config().MessageExportSettings.ExportFromTimestamp
|
|
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.MessageExportSettings.EnableExport = messageExportEnabled
|
|
*cfg.MessageExportSettings.ExportFromTimestamp = messageExportTimestamp
|
|
})
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.MessageExportSettings.EnableExport = false
|
|
*cfg.MessageExportSettings.ExportFromTimestamp = int64(0)
|
|
})
|
|
|
|
// Turn it on, timestamp should be updated.
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
*cfg.MessageExportSettings.EnableExport = true
|
|
_, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, *th.App.Config().MessageExportSettings.EnableExport)
|
|
assert.NotEqual(t, int64(0), *th.App.Config().MessageExportSettings.ExportFromTimestamp)
|
|
|
|
// Turn it off, timestamp should be cleared.
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
*cfg.MessageExportSettings.EnableExport = false
|
|
_, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, *th.App.Config().MessageExportSettings.EnableExport)
|
|
assert.Equal(t, int64(0), *th.App.Config().MessageExportSettings.ExportFromTimestamp)
|
|
|
|
// Set a value from the config file.
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.MessageExportSettings.EnableExport = false
|
|
*cfg.MessageExportSettings.ExportFromTimestamp = int64(12345)
|
|
})
|
|
|
|
// Turn it on, timestamp should *not* be updated.
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
*cfg.MessageExportSettings.EnableExport = true
|
|
_, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, *th.App.Config().MessageExportSettings.EnableExport)
|
|
assert.Equal(t, int64(12345), *th.App.Config().MessageExportSettings.ExportFromTimestamp)
|
|
|
|
// Turn it off, timestamp should be cleared.
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
*cfg.MessageExportSettings.EnableExport = false
|
|
_, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, *th.App.Config().MessageExportSettings.EnableExport)
|
|
assert.Equal(t, int64(0), *th.App.Config().MessageExportSettings.ExportFromTimestamp)
|
|
}
|
|
|
|
func TestUpdateConfigRestrictSystemAdmin(t *testing.T) {
|
|
th := Setup(t)
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
t.Run("Restrict flag should be honored for sysadmin", func(t *testing.T) {
|
|
originalCfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
cfg := originalCfg.Clone()
|
|
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
|
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
|
|
|
returnedCfg, _, err := th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
|
require.Equal(t, *originalCfg.ServiceSettings.SiteURL, *returnedCfg.ServiceSettings.SiteURL)
|
|
|
|
actualCfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, returnedCfg, actualCfg)
|
|
})
|
|
|
|
t.Run("Restrict flag should be ignored by local mode", func(t *testing.T) {
|
|
originalCfg, _, err := th.LocalClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
cfg := originalCfg.Clone()
|
|
*cfg.TeamSettings.SiteName = "MyFancyName" // Allowed
|
|
*cfg.ServiceSettings.SiteURL = "http://example.com" // Ignored
|
|
|
|
returnedCfg, _, err := th.LocalClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "MyFancyName", *returnedCfg.TeamSettings.SiteName)
|
|
require.Equal(t, "http://example.com", *returnedCfg.ServiceSettings.SiteURL)
|
|
})
|
|
}
|
|
|
|
func TestUpdateConfigDiffInAuditRecord(t *testing.T) {
|
|
logFile, err := os.CreateTemp("", "adv.log")
|
|
require.NoError(t, err)
|
|
defer os.Remove(logFile.Name())
|
|
|
|
options := []app.Option{app.WithLicense(model.NewTestLicense("advanced_logging"))}
|
|
th := SetupWithServerOptionsAndConfig(t, options, func(cfg *model.Config) {
|
|
cfg.ExperimentalAuditSettings.FileEnabled = model.NewPointer(true)
|
|
cfg.ExperimentalAuditSettings.FileName = model.NewPointer(logFile.Name())
|
|
})
|
|
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
timeoutVal := *cfg.ServiceSettings.ReadTimeout
|
|
cfg.ServiceSettings.ReadTimeout = model.NewPointer(timeoutVal + 1)
|
|
cfg, _, err = th.SystemAdminClient.UpdateConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.ServiceSettings.ReadTimeout = model.NewPointer(timeoutVal)
|
|
})
|
|
require.Equal(t, timeoutVal+1, *cfg.ServiceSettings.ReadTimeout)
|
|
|
|
// Forcing a flush before attempting to read log's content.
|
|
err = th.Server.Audit.Flush()
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, logFile.Sync())
|
|
|
|
data, err := io.ReadAll(logFile)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, data)
|
|
|
|
entry := FindAuditEntry(string(data), "updateConfig", "")
|
|
require.NotNil(t, entry, "should find an updateConfig audit entry")
|
|
// Verify config diffs are in the raw entry
|
|
require.Contains(t, fmt.Sprintf("%v", entry.Raw),
|
|
fmt.Sprintf("actual_val:%d", timeoutVal+1))
|
|
require.Contains(t, fmt.Sprintf("%v", entry.Raw),
|
|
fmt.Sprintf("base_val:%d", timeoutVal))
|
|
}
|
|
|
|
func TestGetEnvironmentConfig(t *testing.T) {
|
|
// These MUST be t.Setenv (not UpdateConfig) because GetEnvironmentConfig
|
|
// returns only config values sourced from environment variables.
|
|
// t.Setenv prevents t.Parallel — intentionally serial.
|
|
t.Setenv("MM_SERVICESETTINGS_SITEURL", "http://example.mattermost.com")
|
|
t.Setenv("MM_SERVICESETTINGS_ENABLECUSTOMEMOJI", "true")
|
|
|
|
th := Setup(t)
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
SystemAdminClient := th.SystemAdminClient
|
|
|
|
envConfig, _, err := SystemAdminClient.GetEnvironmentConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
serviceSettings, ok := envConfig["ServiceSettings"]
|
|
require.True(t, ok, "should've returned ServiceSettings")
|
|
|
|
serviceSettingsAsMap, ok := serviceSettings.(map[string]any)
|
|
require.True(t, ok, "should've returned ServiceSettings as a map")
|
|
|
|
siteURL, ok := serviceSettingsAsMap["SiteURL"]
|
|
require.True(t, ok, "should've returned ServiceSettings.SiteURL")
|
|
|
|
siteURLAsBool, ok := siteURL.(bool)
|
|
require.True(t, ok, "should've returned ServiceSettings.SiteURL as a boolean")
|
|
require.True(t, siteURLAsBool, "should've returned ServiceSettings.SiteURL as true")
|
|
|
|
enableCustomEmoji, ok := serviceSettingsAsMap["EnableCustomEmoji"]
|
|
require.True(t, ok, "should've returned ServiceSettings.EnableCustomEmoji")
|
|
|
|
enableCustomEmojiAsBool, ok := enableCustomEmoji.(bool)
|
|
require.True(t, ok, "should've returned ServiceSettings.EnableCustomEmoji as a boolean")
|
|
require.True(t, enableCustomEmojiAsBool, "should've returned ServiceSettings.EnableCustomEmoji as true")
|
|
|
|
_, ok = envConfig["TeamSettings"]
|
|
require.False(t, ok, "should not have returned TeamSettings")
|
|
})
|
|
|
|
t.Run("as team admin", func(t *testing.T) {
|
|
TeamAdminClient := th.CreateClient()
|
|
th.LoginTeamAdminWithClient(t, TeamAdminClient)
|
|
|
|
envConfig, _, err := TeamAdminClient.GetEnvironmentConfig(context.Background())
|
|
require.NoError(t, err)
|
|
require.Empty(t, envConfig)
|
|
})
|
|
|
|
t.Run("as regular user", func(t *testing.T) {
|
|
client := th.Client
|
|
|
|
envConfig, _, err := client.GetEnvironmentConfig(context.Background())
|
|
require.NoError(t, err)
|
|
require.Empty(t, envConfig)
|
|
})
|
|
|
|
t.Run("as not-regular user", func(t *testing.T) {
|
|
client := th.CreateClient()
|
|
|
|
_, resp, err := client.GetEnvironmentConfig(context.Background())
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestGetClientConfig(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
testKey := "supersecretkey"
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.GoogleDeveloperKey = testKey })
|
|
|
|
t.Run("with session", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.GoogleDeveloperKey = testKey
|
|
})
|
|
|
|
client := th.Client
|
|
|
|
config, _, err := client.GetClientConfig(context.Background(), "")
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, config["Version"], "config not returned correctly")
|
|
require.Equal(t, testKey, config["GoogleDeveloperKey"])
|
|
})
|
|
|
|
t.Run("without session", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ServiceSettings.GoogleDeveloperKey = testKey
|
|
})
|
|
|
|
client := th.CreateClient()
|
|
|
|
config, _, err := client.GetClientConfig(context.Background(), "")
|
|
require.NoError(t, err)
|
|
|
|
require.NotEmpty(t, config["Version"], "config not returned correctly")
|
|
require.Empty(t, config["GoogleDeveloperKey"], "config should be missing developer key")
|
|
})
|
|
|
|
t.Run("format=old (backward compatibility)", func(t *testing.T) {
|
|
client := th.Client
|
|
|
|
resp, err := client.DoAPIGet(context.Background(), "/config/client?format=old", "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var config map[string]string
|
|
err = json.NewDecoder(resp.Body).Decode(&config)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, config["Version"], "config not returned correctly")
|
|
})
|
|
|
|
t.Run("format=junk (ignored)", func(t *testing.T) {
|
|
client := th.Client
|
|
|
|
resp, err := client.DoAPIGet(context.Background(), "/config/client?format=junk", "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var config map[string]string
|
|
err = json.NewDecoder(resp.Body).Decode(&config)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, config["Version"], "config not returned correctly")
|
|
})
|
|
}
|
|
|
|
func TestPatchConfig(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
// Ensure ConsoleLevel is set to DEBUG
|
|
config := model.Config{LogSettings: model.LogSettings{
|
|
ConsoleLevel: model.NewPointer("DEBUG"),
|
|
}}
|
|
_, _, err := th.SystemAdminClient.PatchConfig(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("config is missing", func(t *testing.T) {
|
|
_, response, err := th.Client.PatchConfig(context.Background(), nil)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, response)
|
|
})
|
|
|
|
t.Run("user is not system admin", func(t *testing.T) {
|
|
_, response, err := th.Client.PatchConfig(context.Background(), &model.Config{})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, response)
|
|
})
|
|
|
|
t.Run("should not update the restricted fields when restrict toggle is on for sysadmin", func(t *testing.T) {
|
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
|
|
|
config := model.Config{LogSettings: model.LogSettings{
|
|
ConsoleLevel: model.NewPointer("INFO"),
|
|
}}
|
|
|
|
updatedConfig, _, _ := th.SystemAdminClient.PatchConfig(context.Background(), &config)
|
|
|
|
assert.Equal(t, "DEBUG", *updatedConfig.LogSettings.ConsoleLevel)
|
|
})
|
|
|
|
t.Run("should not bypass the restrict toggle if local client", func(t *testing.T) {
|
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
|
|
|
|
config := model.Config{LogSettings: model.LogSettings{
|
|
ConsoleLevel: model.NewPointer("INFO"),
|
|
}}
|
|
|
|
oldConfig, _, _ := th.LocalClient.GetConfig(context.Background())
|
|
updatedConfig, _, _ := th.LocalClient.PatchConfig(context.Background(), &config)
|
|
|
|
assert.Equal(t, "INFO", *updatedConfig.LogSettings.ConsoleLevel)
|
|
// reset the config
|
|
_, _, err := th.LocalClient.UpdateConfig(context.Background(), oldConfig)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
t.Run("check if config is valid", func(t *testing.T) {
|
|
config := model.Config{PasswordSettings: model.PasswordSettings{
|
|
MinimumLength: model.NewPointer(4),
|
|
}}
|
|
|
|
_, response, err := client.PatchConfig(context.Background(), &config)
|
|
|
|
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
|
|
assert.Error(t, err)
|
|
CheckErrorID(t, err, "model.config.is_valid.password_length.app_error")
|
|
})
|
|
|
|
t.Run("should patch the config", func(t *testing.T) {
|
|
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = false
|
|
th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalDefaultChannels = []string{"some-channel"} })
|
|
|
|
oldConfig, _, err := client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, *oldConfig.PasswordSettings.Lowercase)
|
|
assert.NotEqual(t, 15, *oldConfig.PasswordSettings.MinimumLength)
|
|
assert.Equal(t, "DEBUG", *oldConfig.LogSettings.ConsoleLevel)
|
|
assert.True(t, oldConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
|
|
|
states := make(map[string]*model.PluginState)
|
|
states["com.mattermost.nps"] = &model.PluginState{Enable: *model.NewPointer(false)}
|
|
config := model.Config{PasswordSettings: model.PasswordSettings{
|
|
Lowercase: model.NewPointer(true),
|
|
MinimumLength: model.NewPointer(15),
|
|
}, LogSettings: model.LogSettings{
|
|
ConsoleLevel: model.NewPointer("INFO"),
|
|
},
|
|
TeamSettings: model.TeamSettings{
|
|
ExperimentalDefaultChannels: []string{"another-channel"},
|
|
},
|
|
PluginSettings: model.PluginSettings{
|
|
PluginStates: states,
|
|
},
|
|
}
|
|
|
|
_, response, err := client.PatchConfig(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
|
|
updatedConfig, _, err := client.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
assert.True(t, *updatedConfig.PasswordSettings.Lowercase)
|
|
assert.Equal(t, "INFO", *updatedConfig.LogSettings.ConsoleLevel)
|
|
assert.Equal(t, []string{"another-channel"}, updatedConfig.TeamSettings.ExperimentalDefaultChannels)
|
|
assert.False(t, updatedConfig.PluginSettings.PluginStates["com.mattermost.nps"].Enable)
|
|
assert.Equal(t, "no-cache, no-store, must-revalidate", response.Header.Get("Cache-Control"))
|
|
|
|
// reset the config
|
|
_, _, err = client.UpdateConfig(context.Background(), oldConfig)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("should sanitize config", func(t *testing.T) {
|
|
config := model.Config{PasswordSettings: model.PasswordSettings{
|
|
Symbol: model.NewPointer(true),
|
|
}}
|
|
|
|
updatedConfig, _, err := client.PatchConfig(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, model.FakeSetting, *updatedConfig.SqlSettings.DataSource)
|
|
})
|
|
|
|
t.Run("not allowing to toggle enable uploads for plugin via api", func(t *testing.T) {
|
|
config := model.Config{PluginSettings: model.PluginSettings{
|
|
EnableUploads: model.NewPointer(true),
|
|
}}
|
|
|
|
updatedConfig, resp, err := client.PatchConfig(context.Background(), &config)
|
|
if client == th.LocalClient {
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Equal(t, true, *updatedConfig.PluginSettings.EnableUploads)
|
|
} else {
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
}
|
|
})
|
|
|
|
t.Run("not allowing to change import directory via api, unless local mode", func(t *testing.T) {
|
|
oldDirectory := *th.App.Config().ImportSettings.Directory
|
|
config := model.Config{ImportSettings: model.ImportSettings{
|
|
Directory: model.NewPointer("./new-import-dir"),
|
|
}}
|
|
|
|
updatedConfig, resp, err := client.PatchConfig(context.Background(), &config)
|
|
if client == th.LocalClient {
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
assert.Equal(t, "./new-import-dir", *updatedConfig.ImportSettings.Directory)
|
|
} else {
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
}
|
|
|
|
// Reset for local mode
|
|
if client == th.LocalClient {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ImportSettings.Directory = oldDirectory
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("Should not be able to modify PluginSettings.MarketplaceURL if EnableUploads is disabled", func(t *testing.T) {
|
|
oldURL := "hello.com"
|
|
newURL := "new.com"
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableUploads = false
|
|
*cfg.PluginSettings.MarketplaceURL = oldURL
|
|
})
|
|
|
|
cfg := th.App.Config().Clone()
|
|
*cfg.PluginSettings.MarketplaceURL = newURL
|
|
|
|
_, _, err := th.SystemAdminClient.PatchConfig(context.Background(), cfg)
|
|
require.Error(t, err)
|
|
|
|
// Allowing uploads
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.EnableUploads = true
|
|
*cfg.PluginSettings.MarketplaceURL = oldURL
|
|
})
|
|
|
|
cfg = th.App.Config().Clone()
|
|
*cfg.PluginSettings.MarketplaceURL = newURL
|
|
|
|
cfg, _, err = th.SystemAdminClient.PatchConfig(context.Background(), cfg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, newURL, *cfg.PluginSettings.MarketplaceURL)
|
|
})
|
|
|
|
t.Run("System Admin should not be able to clear Site URL", func(t *testing.T) {
|
|
cfg, _, err := th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
siteURL := cfg.ServiceSettings.SiteURL
|
|
defer th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.SiteURL = siteURL })
|
|
|
|
// Set the SiteURL
|
|
nonEmptyURL := "http://localhost"
|
|
config := model.Config{
|
|
ServiceSettings: model.ServiceSettings{
|
|
SiteURL: model.NewPointer(nonEmptyURL),
|
|
},
|
|
}
|
|
updatedConfig, _, err := th.SystemAdminClient.PatchConfig(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
require.Equal(t, nonEmptyURL, *updatedConfig.ServiceSettings.SiteURL)
|
|
|
|
// Check that the Site URL can't be cleared
|
|
config = model.Config{
|
|
ServiceSettings: model.ServiceSettings{
|
|
SiteURL: model.NewPointer(""),
|
|
},
|
|
}
|
|
_, resp, err := th.SystemAdminClient.PatchConfig(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorID(t, err, "api.config.update_config.clear_siteurl.app_error")
|
|
|
|
// Check that the Site URL wasn't cleared
|
|
cfg, _, err = th.SystemAdminClient.GetConfig(context.Background())
|
|
require.NoError(t, err)
|
|
require.Equal(t, nonEmptyURL, *cfg.ServiceSettings.SiteURL)
|
|
|
|
// Check that sending an empty config returns no error.
|
|
_, _, err = th.SystemAdminClient.PatchConfig(context.Background(), &model.Config{})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("should preserve plugin configs when toggling plugin enable off then on", func(t *testing.T) {
|
|
// Have some plugin settings setup
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.PluginSettings.Enable = model.NewPointer(true)
|
|
cfg.PluginSettings.Plugins = map[string]map[string]any{
|
|
"com.example.oauth-plugin": {
|
|
"clientid": "test-client-id",
|
|
"clientsecret": "test-client-secret",
|
|
},
|
|
}
|
|
})
|
|
|
|
// First PATCH: disable the plugin subsystem
|
|
disablePatch := &model.Config{}
|
|
disablePatch.PluginSettings.Enable = model.NewPointer(false)
|
|
disabledResponse, _, err := th.SystemAdminClient.PatchConfig(context.Background(), disablePatch)
|
|
require.NoError(t, err)
|
|
// The sanitized response returns an empty Plugins map when plugins are disabled
|
|
assert.Empty(t, disabledResponse.PluginSettings.Plugins)
|
|
|
|
// Second PATCH: re-enable plugins using the response from the first PATCH
|
|
disabledResponse.PluginSettings.Enable = model.NewPointer(true)
|
|
_, _, err = th.SystemAdminClient.PatchConfig(context.Background(), &model.Config{
|
|
PluginSettings: disabledResponse.PluginSettings,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Plugin configs must survive the round-trip unchanged
|
|
storedCfg := th.App.Config()
|
|
require.Contains(t, storedCfg.PluginSettings.Plugins, "com.example.oauth-plugin")
|
|
assert.Equal(t, "test-client-id", storedCfg.PluginSettings.Plugins["com.example.oauth-plugin"]["clientid"])
|
|
})
|
|
|
|
t.Run("local client should preserve plugin configs when toggling plugin enable off then on", func(t *testing.T) {
|
|
// Have some plugin settings setup
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.PluginSettings.Enable = model.NewPointer(true)
|
|
cfg.PluginSettings.Plugins = map[string]map[string]any{
|
|
"com.example.oauth-plugin": {
|
|
"clientid": "test-client-id",
|
|
"clientsecret": "test-client-secret",
|
|
},
|
|
}
|
|
})
|
|
|
|
// First PATCH: disable the plugin subsystem
|
|
disablePatch := &model.Config{}
|
|
disablePatch.PluginSettings.Enable = model.NewPointer(false)
|
|
disabledResponse, _, err := th.LocalClient.PatchConfig(context.Background(), disablePatch)
|
|
require.NoError(t, err)
|
|
// The sanitized response returns an empty Plugins map when plugins are disabled
|
|
assert.Empty(t, disabledResponse.PluginSettings.Plugins)
|
|
|
|
// Second PATCH: re-enable plugins using the response from the first PATCH
|
|
disabledResponse.PluginSettings.Enable = model.NewPointer(true)
|
|
_, _, err = th.LocalClient.PatchConfig(context.Background(), &model.Config{
|
|
PluginSettings: disabledResponse.PluginSettings,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Plugin configs must survive the round-trip unchanged
|
|
storedCfg := th.App.Config()
|
|
require.Contains(t, storedCfg.PluginSettings.Plugins, "com.example.oauth-plugin")
|
|
assert.Equal(t, "test-client-id", storedCfg.PluginSettings.Plugins["com.example.oauth-plugin"]["clientid"])
|
|
})
|
|
}
|
|
|
|
func TestMigrateConfig(t *testing.T) {
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
t.Run("LocalClient", func(t *testing.T) {
|
|
cfg := &model.Config{}
|
|
cfg.SetDefaults()
|
|
|
|
file, err := json.MarshalIndent(cfg, "", " ")
|
|
require.NoError(t, err)
|
|
|
|
err = os.WriteFile("from.json", file, 0644)
|
|
require.NoError(t, err)
|
|
|
|
defer os.Remove("from.json")
|
|
|
|
f, err := config.NewStoreFromDSN("from.json", false, nil, false)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err = f.RemoveFile("from.json")
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
_, err = config.NewStoreFromDSN("to.json", false, nil, true)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
err = f.RemoveFile("to.json")
|
|
require.NoError(t, err)
|
|
}()
|
|
|
|
_, err = th.LocalClient.MigrateConfig(context.Background(), "from.json", "to.json")
|
|
require.NoError(t, err)
|
|
})
|
|
}
|