diff --git a/api4/apitestlib.go b/api4/apitestlib.go index b9159f1c08..49a4b0405b 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -96,7 +96,7 @@ func setupTestHelper(dbStore store.Store, searchEngine *searchengine.Broker, ent } memoryStore.Set(memoryConfig) - configStore, err := config.NewStoreFromBacking(memoryStore, nil) + configStore, err := config.NewStoreFromBacking(memoryStore, nil, false) if err != nil { panic(err) } diff --git a/api4/config_test.go b/api4/config_test.go index e6c305e9e6..3d3a5f0d24 100644 --- a/api4/config_test.go +++ b/api4/config_test.go @@ -691,11 +691,11 @@ func TestMigrateConfig(t *testing.T) { }) th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) { - f, err := config.NewStore("from.json", false, nil) + f, err := config.NewStore("from.json", false, false, nil) require.NoError(t, err) defer f.RemoveFile("from.json") - _, err = config.NewStore("to.json", false, nil) + _, err = config.NewStore("to.json", false, false, nil) require.NoError(t, err) defer f.RemoveFile("to.json") diff --git a/app/config.go b/app/config.go index 83ec86cea3..8c536932b4 100644 --- a/app/config.go +++ b/app/config.go @@ -46,6 +46,9 @@ func (a *App) EnvironmentConfig() map[string]interface{} { } func (s *Server) UpdateConfig(f func(*model.Config)) { + if s.configStore.IsReadOnly() { + return + } old := s.Config() updated := old.Clone() f(updated) diff --git a/app/options.go b/app/options.go index 431ab2a998..b5c88a22d8 100644 --- a/app/options.go +++ b/app/options.go @@ -43,9 +43,9 @@ func StoreOverride(override interface{}) Option { // or a database connection string. It receives as well a set of // custom defaults that will be applied for any unset property of the // config loaded from the dsn on top of the normal defaults -func Config(dsn string, watch bool, configDefaults *model.Config) Option { +func Config(dsn string, watch, readOnly bool, configDefaults *model.Config) Option { return func(s *Server) error { - configStore, err := config.NewStore(dsn, watch, configDefaults) + configStore, err := config.NewStore(dsn, watch, readOnly, configDefaults) if err != nil { return errors.Wrap(err, "failed to apply Config option") } diff --git a/app/server.go b/app/server.go index 56976c7979..e5afca8031 100644 --- a/app/server.go +++ b/app/server.go @@ -210,7 +210,7 @@ func NewServer(options ...Option) (*Server, error) { if err != nil { return nil, errors.Wrap(err, "failed to load config") } - configStore, err := config.NewStoreFromBacking(innerStore, nil) + configStore, err := config.NewStoreFromBacking(innerStore, nil, false) if err != nil { return nil, errors.Wrap(err, "failed to load config") } diff --git a/app/server_test.go b/app/server_test.go index 4c3d70c2e5..f6b4f7e668 100644 --- a/app/server_test.go +++ b/app/server_test.go @@ -384,7 +384,7 @@ func TestSentry(t *testing.T) { s, err := NewServer(func(server *Server) error { configStore, _ := config.NewFileStore("config.json", true) - store, _ := config.NewStoreFromBacking(configStore, nil) + store, _ := config.NewStoreFromBacking(configStore, nil, false) server.configStore = store server.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" @@ -435,7 +435,7 @@ func TestSentry(t *testing.T) { s, err := NewServer(func(server *Server) error { configStore, _ := config.NewFileStore("config.json", true) - store, _ := config.NewStoreFromBacking(configStore, nil) + store, _ := config.NewStoreFromBacking(configStore, nil, false) server.configStore = store server.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" diff --git a/cmd/mattermost/commands/config.go b/cmd/mattermost/commands/config.go index 05035c5fde..39b3f476eb 100644 --- a/cmd/mattermost/commands/config.go +++ b/cmd/mattermost/commands/config.go @@ -145,7 +145,7 @@ func getConfigStore(command *cobra.Command) (*config.Store, error) { return nil, errors.Wrap(err, "failed to initialize i18n") } - configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, nil) + configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, false, nil) if err != nil { return nil, errors.Wrap(err, "failed to initialize config store") } diff --git a/cmd/mattermost/commands/config_test.go b/cmd/mattermost/commands/config_test.go index b557d08b49..11c5b88bea 100644 --- a/cmd/mattermost/commands/config_test.go +++ b/cmd/mattermost/commands/config_test.go @@ -543,9 +543,9 @@ func TestConfigMigrate(t *testing.T) { sqlDSN := getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource) fileDSN := "config.json" - ds, err := config.NewStore(sqlDSN, false, nil) + ds, err := config.NewStore(sqlDSN, false, false, nil) require.NoError(t, err) - fs, err := config.NewStore(fileDSN, false, nil) + fs, err := config.NewStore(fileDSN, false, false, nil) require.NoError(t, err) defer ds.Close() diff --git a/cmd/mattermost/commands/db.go b/cmd/mattermost/commands/db.go index d8cc1758b7..511d996308 100644 --- a/cmd/mattermost/commands/db.go +++ b/cmd/mattermost/commands/db.go @@ -57,7 +57,7 @@ func initDbCmdF(command *cobra.Command, _ []string) error { return errors.Wrap(err, "error loading custom configuration defaults") } - configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, customDefaults) + configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), false, false, customDefaults) if err != nil { return errors.Wrap(err, "failed to load configuration") } diff --git a/cmd/mattermost/commands/init.go b/cmd/mattermost/commands/init.go index 5c24f87a81..1b912051c7 100644 --- a/cmd/mattermost/commands/init.go +++ b/cmd/mattermost/commands/init.go @@ -12,8 +12,8 @@ import ( "github.com/mattermost/mattermost-server/v5/utils" ) -func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) { - a, err := InitDBCommandContext(getConfigDSN(command, config.GetEnvironment())) +func initDBCommandContextCobra(command *cobra.Command, readOnlyConfigStore bool) (*app.App, error) { + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), readOnlyConfigStore) if err != nil { // Returning an error just prints the usage message, so actually panic panic(err) @@ -25,14 +25,22 @@ func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) { return a, nil } -func InitDBCommandContext(configDSN string) (*app.App, error) { +func InitDBCommandContextCobra(command *cobra.Command) (*app.App, error) { + return initDBCommandContextCobra(command, true) +} + +func InitDBCommandContextCobraReadWrite(command *cobra.Command) (*app.App, error) { + return initDBCommandContextCobra(command, false) +} + +func initDBCommandContext(configDSN string, readOnlyConfigStore bool) (*app.App, error) { if err := utils.TranslationsPreInit(); err != nil { return nil, err } model.AppErrorInit(utils.T) s, err := app.NewServer( - app.Config(configDSN, false, nil), + app.Config(configDSN, false, readOnlyConfigStore, nil), app.StartSearchEngine, ) if err != nil { diff --git a/cmd/mattermost/commands/jobserver.go b/cmd/mattermost/commands/jobserver.go index 177684eded..1949ec63b2 100644 --- a/cmd/mattermost/commands/jobserver.go +++ b/cmd/mattermost/commands/jobserver.go @@ -34,7 +34,7 @@ func jobserverCmdF(command *cobra.Command, args []string) error { noSchedule, _ := command.Flags().GetBool("noschedule") // Initialize - a, err := InitDBCommandContext(getConfigDSN(command, config.GetEnvironment())) + a, err := initDBCommandContext(getConfigDSN(command, config.GetEnvironment()), false) if err != nil { return err } diff --git a/cmd/mattermost/commands/plugin.go b/cmd/mattermost/commands/plugin.go index d0af4a6a10..92b9136fda 100644 --- a/cmd/mattermost/commands/plugin.go +++ b/cmd/mattermost/commands/plugin.go @@ -104,7 +104,7 @@ func init() { } func pluginAddCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } @@ -134,7 +134,7 @@ func pluginAddCmdF(command *cobra.Command, args []string) error { } func pluginDeleteCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } @@ -158,7 +158,7 @@ func pluginDeleteCmdF(command *cobra.Command, args []string) error { } func pluginEnableCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } @@ -182,7 +182,7 @@ func pluginEnableCmdF(command *cobra.Command, args []string) error { } func pluginDisableCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } @@ -265,7 +265,7 @@ func pluginPublicKeysCmdF(command *cobra.Command, args []string) error { } func pluginAddPublicKeyCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } @@ -296,7 +296,7 @@ func pluginAddPublicKeyCmdF(command *cobra.Command, args []string) error { } func pluginDeletePublicKeyCmdF(command *cobra.Command, args []string) error { - a, err := InitDBCommandContextCobra(command) + a, err := InitDBCommandContextCobraReadWrite(command) if err != nil { return err } diff --git a/cmd/mattermost/commands/plugin_test.go b/cmd/mattermost/commands/plugin_test.go index 4e388ee928..491dc66a8b 100644 --- a/cmd/mattermost/commands/plugin_test.go +++ b/cmd/mattermost/commands/plugin_test.go @@ -38,7 +38,7 @@ func TestPlugin(t *testing.T) { fs, err := config.NewFileStore(th.ConfigPath(), false) require.Nil(t, err) - cfsStore, err := config.NewStoreFromBacking(fs, nil) + cfsStore, err := config.NewStoreFromBacking(fs, nil, false) require.Nil(t, err) require.NotNil(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"]) assert.True(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"].Enable) @@ -48,7 +48,7 @@ func TestPlugin(t *testing.T) { assert.Contains(t, output, "Disabled plugin: testplugin") fs, err = config.NewFileStore(th.ConfigPath(), false) require.Nil(t, err) - cfsStore, err = config.NewStoreFromBacking(fs, nil) + cfsStore, err = config.NewStoreFromBacking(fs, nil, false) require.Nil(t, err) require.NotNil(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"]) assert.False(t, cfsStore.Get().PluginSettings.PluginStates["testplugin"].Enable) diff --git a/cmd/mattermost/commands/server.go b/cmd/mattermost/commands/server.go index 2c7f1dee52..aea5712237 100644 --- a/cmd/mattermost/commands/server.go +++ b/cmd/mattermost/commands/server.go @@ -50,7 +50,7 @@ func serverCmdF(command *cobra.Command, args []string) error { mlog.Warn("Error loading custom configuration defaults: " + err.Error()) } - configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), !disableConfigWatch, customDefaults) + configStore, err := config.NewStore(getConfigDSN(command, config.GetEnvironment()), !disableConfigWatch, false, customDefaults) if err != nil { return errors.Wrap(err, "failed to load configuration") } diff --git a/config/common_test.go b/config/common_test.go index 5deef2b67a..603ce6213c 100644 --- a/config/common_test.go +++ b/config/common_test.go @@ -140,7 +140,7 @@ func TestMergeConfigs(t *testing.T) { func TestConfigEnvironmentOverrides(t *testing.T) { memstore, err := config.NewMemoryStore() require.NoError(t, err) - base, err := config.NewStoreFromBacking(memstore, nil) + base, err := config.NewStoreFromBacking(memstore, nil, false) require.NoError(t, err) originalConfig := &model.Config{} originalConfig.ServiceSettings.SiteURL = newString("http://notoverriden.ca") @@ -169,7 +169,7 @@ func TestRemoveEnvironmentOverrides(t *testing.T) { memstore, err := config.NewMemoryStore() require.NoError(t, err) - base, err := config.NewStoreFromBacking(memstore, nil) + base, err := config.NewStoreFromBacking(memstore, nil, false) require.NoError(t, err) oldCfg := base.Get() assert.Equal(t, "http://overridden.ca", *oldCfg.ServiceSettings.SiteURL) diff --git a/config/database_test.go b/config/database_test.go index 8f1572eec4..de5e459ca4 100644 --- a/config/database_test.go +++ b/config/database_test.go @@ -120,7 +120,7 @@ func newTestDatabaseStore(t *testing.T, customDefaults *model.Config) (*config.S dss, err := config.NewDatabaseStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource)) require.NoError(t, err) - cStore, err := config.NewStoreFromBacking(dss, customDefaults) + cStore, err := config.NewStoreFromBacking(dss, customDefaults, false) require.NoError(t, err) return cStore, nil diff --git a/config/file_test.go b/config/file_test.go index c52319c0dc..34f2467940 100644 --- a/config/file_test.go +++ b/config/file_test.go @@ -91,7 +91,7 @@ func TestFileStoreNew(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -105,7 +105,7 @@ func TestFileStoreNew(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults) + configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults, false) require.NoError(t, err) defer configStore.Close() @@ -124,7 +124,7 @@ func TestFileStoreNew(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -138,7 +138,7 @@ func TestFileStoreNew(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults) + configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults, false) require.NoError(t, err) defer configStore.Close() @@ -160,7 +160,7 @@ func TestFileStoreNew(t *testing.T) { path := filepath.Join(tempDir, "does_not_exist") fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -179,7 +179,7 @@ func TestFileStoreNew(t *testing.T) { path := filepath.Join(tempDir, "does_not_exist") fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults) + configStore, err := config.NewStoreFromBacking(fs, customConfigDefaults, false) require.NoError(t, err) defer configStore.Close() @@ -198,7 +198,7 @@ func TestFileStoreNew(t *testing.T) { path := filepath.Join(tempDir, "does/not/exist") fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.Nil(t, configStore) require.Error(t, err) }) @@ -220,7 +220,7 @@ func TestFileStoreNew(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -239,7 +239,7 @@ func TestFileStoreNew(t *testing.T) { path := "TestFileStoreNew/a/b/c/config.json" fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -254,7 +254,7 @@ func TestFileStoreGet(t *testing.T) { fs, err := config.NewFileStore(path, false) require.NoError(t, err) - configStore, err := config.NewStoreFromBacking(fs, nil) + configStore, err := config.NewStoreFromBacking(fs, nil, false) require.NoError(t, err) defer configStore.Close() @@ -280,7 +280,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -292,7 +292,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -306,7 +306,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, customConfigDefaults) + fs, err := config.NewStoreFromBacking(fsInner, customConfigDefaults, false) require.NoError(t, err) defer fs.Close() @@ -318,7 +318,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, customConfigDefaults) + fs, err = config.NewStoreFromBacking(fsInner, customConfigDefaults, false) require.NoError(t, err) defer fs.Close() @@ -333,7 +333,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -345,7 +345,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -359,7 +359,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -371,7 +371,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -385,7 +385,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -397,7 +397,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -411,7 +411,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -423,7 +423,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -440,7 +440,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -452,7 +452,7 @@ func TestFileStoreGetEnivironmentOverrides(t *testing.T) { fsInner, err = config.NewFileStore(path, false) require.NoError(t, err) - fs, err = config.NewStoreFromBacking(fsInner, nil) + fs, err = config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -468,7 +468,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -489,7 +489,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -508,7 +508,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -529,7 +529,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -548,7 +548,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -570,7 +570,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -598,7 +598,7 @@ func TestFileStoreSet(t *testing.T) { fsInner, err := config.NewFileStore(path, true) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -630,7 +630,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -647,7 +647,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -671,7 +671,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -696,7 +696,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -721,7 +721,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -746,7 +746,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -771,7 +771,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -798,7 +798,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -820,7 +820,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -841,7 +841,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -860,7 +860,7 @@ func TestFileStoreLoad(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -887,7 +887,7 @@ func TestFileStoreWatcherEmitter(t *testing.T) { defer tearDown() fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -913,7 +913,7 @@ func TestFileStoreWatcherEmitter(t *testing.T) { defer tearDown() fsInner, err := config.NewFileStore(path, true) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -938,7 +938,7 @@ func TestFileStoreSave(t *testing.T) { fsInner, err := config.NewFileStore(path, false) require.NoError(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.NoError(t, err) defer fs.Close() @@ -1262,3 +1262,25 @@ func wasCalled(c chan bool, duration time.Duration) bool { } return false } + +func TestFileStoreReadOnly(t *testing.T) { + path, tearDown := setupConfigFile(t, emptyConfig) + defer tearDown() + fsInner, err := config.NewFileStore(path, true) + require.NoError(t, err) + fs, err := config.NewStoreFromBacking(fsInner, nil, true) + require.NoError(t, err) + defer fs.Close() + + called := make(chan bool, 1) + callback := func(oldfg, newCfg *model.Config) { + called <- true + } + fs.AddListener(callback) + + cfg, err := fs.Set(minimalConfig) + require.Nil(t, cfg) + require.Equal(t, config.ErrReadOnlyStore, err) + + require.False(t, wasCalled(called, 1*time.Second), "callback should not have been called since config is read-only") +} diff --git a/config/migrate.go b/config/migrate.go index 44b116db00..fac71f72e2 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -9,13 +9,13 @@ import ( // Migrate migrates SAML keys, certificates, and other config files from one store to another given their data source names. func Migrate(from, to string) error { - source, err := NewStore(from, false, nil) + source, err := NewStore(from, false, false, nil) if err != nil { return errors.Wrapf(err, "failed to access source config %s", from) } defer source.Close() - destination, err := NewStore(to, false, nil) + destination, err := NewStore(to, false, false, nil) if err != nil { return errors.Wrapf(err, "failed to access destination config %s", to) } diff --git a/config/migrate_test.go b/config/migrate_test.go index 52844fbc6c..a18614520b 100644 --- a/config/migrate_test.go +++ b/config/migrate_test.go @@ -103,7 +103,7 @@ func TestMigrate(t *testing.T) { sourcedb, err := config.NewDatabaseStore(sourceDSN) require.NoError(t, err) - source, err := config.NewStoreFromBacking(sourcedb, nil) + source, err := config.NewStoreFromBacking(sourcedb, nil, false) require.NoError(t, err) defer source.Close() @@ -113,7 +113,7 @@ func TestMigrate(t *testing.T) { destinationfile, err := config.NewFileStore(destinationDSN, false) require.NoError(t, err) - destination, err := config.NewStoreFromBacking(destinationfile, nil) + destination, err := config.NewStoreFromBacking(destinationfile, nil, false) require.NoError(t, err) defer destination.Close() @@ -132,7 +132,7 @@ func TestMigrate(t *testing.T) { sourcefile, err := config.NewFileStore(sourceDSN, false) require.NoError(t, err) - source, err := config.NewStoreFromBacking(sourcefile, nil) + source, err := config.NewStoreFromBacking(sourcefile, nil, false) require.NoError(t, err) defer source.Close() @@ -142,7 +142,7 @@ func TestMigrate(t *testing.T) { destinationdb, err := config.NewDatabaseStore(destinationDSN) require.NoError(t, err) - destination, err := config.NewStoreFromBacking(destinationdb, nil) + destination, err := config.NewStoreFromBacking(destinationdb, nil, false) require.NoError(t, err) defer destination.Close() diff --git a/config/store.go b/config/store.go index 5e70bcfb12..c685be5fca 100644 --- a/config/store.go +++ b/config/store.go @@ -14,6 +14,12 @@ import ( "github.com/mattermost/mattermost-server/v5/utils/jsonutils" ) +var ( + // ErrReadOnlyStore is returned when an attempt to modify a read-only + // configuration store is made. + ErrReadOnlyStore = errors.New("configuration store is read-only") +) + // Listener is a callback function invoked when the configuration changes. type Listener func(oldConfig *model.Config, newConfig *model.Config) @@ -48,13 +54,13 @@ type BackingStore interface { } // NewStore creates a database or file store given a data source name by which to connect. -func NewStore(dsn string, watch bool, customDefaults *model.Config) (*Store, error) { +func NewStore(dsn string, watch, readOnly bool, customDefaults *model.Config) (*Store, error) { backingStore, err := getBackingStore(dsn, watch) if err != nil { return nil, err } - store, err := NewStoreFromBacking(backingStore, customDefaults) + store, err := NewStoreFromBacking(backingStore, customDefaults, readOnly) if err != nil { backingStore.Close() return nil, errors.Wrap(err, "failed to create store") @@ -63,10 +69,11 @@ func NewStore(dsn string, watch bool, customDefaults *model.Config) (*Store, err return store, nil } -func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config) (*Store, error) { +func NewStoreFromBacking(backingStore BackingStore, customDefaults *model.Config, readOnly bool) (*Store, error) { store := &Store{ backingStore: backingStore, configCustomDefaults: customDefaults, + readOnly: readOnly, } if err := store.Load(); err != nil { @@ -96,7 +103,7 @@ func NewTestMemoryStore() *Store { panic("failed to initialize memory store: " + err.Error()) } - configStore, err := NewStoreFromBacking(memoryStore, nil) + configStore, err := NewStoreFromBacking(memoryStore, nil, false) if err != nil { panic("failed to initialize config store: " + err.Error()) } @@ -114,6 +121,7 @@ type Store struct { configCustomDefaults *model.Config persistFeatureFlags bool + readOnly bool } // Get fetches the current, cached configuration. @@ -156,6 +164,10 @@ func (s *Store) Set(newCfg *model.Config) (*model.Config, error) { var unlockOnce sync.Once defer unlockOnce.Do(s.configLock.Unlock) + if s.readOnly { + return nil, ErrReadOnlyStore + } + oldCfg := s.config.Clone() // Really just for some tests we need to set defaults here @@ -236,7 +248,7 @@ func (s *Store) loadLockedWithOld(oldCfg *model.Config, unlockOnce *sync.Once) e if err != nil { return errors.Wrap(err, "failed to marshal loaded config") } - if len(configBytes) == 0 || !bytes.Equal(oldCfgBytes, newCfgBytes) { + if !s.readOnly && (len(configBytes) == 0 || !bytes.Equal(oldCfgBytes, newCfgBytes)) { if err := s.backingStore.Set(s.configNoEnv); err != nil { if !errors.Is(err, ErrReadOnlyConfiguration) { return errors.Wrap(err, "failed to persist") @@ -276,6 +288,9 @@ func (s *Store) GetFile(name string) ([]byte, error) { func (s *Store) SetFile(name string, data []byte) error { s.configLock.Lock() defer s.configLock.Unlock() + if s.readOnly { + return ErrReadOnlyStore + } return s.backingStore.SetFile(name, data) } @@ -290,6 +305,9 @@ func (s *Store) HasFile(name string) (bool, error) { func (s *Store) RemoveFile(name string) error { s.configLock.Lock() defer s.configLock.Unlock() + if s.readOnly { + return ErrReadOnlyStore + } return s.backingStore.RemoveFile(name) } @@ -304,3 +322,10 @@ func (s *Store) Close() error { defer s.configLock.Unlock() return s.backingStore.Close() } + +// IsReadOnly returns whether or not the store is read-only. +func (s *Store) IsReadOnly() bool { + s.configLock.RLock() + defer s.configLock.RUnlock() + return s.readOnly +} diff --git a/config/store_test.go b/config/store_test.go index 6b586b8d47..998d097845 100644 --- a/config/store_test.go +++ b/config/store_test.go @@ -29,26 +29,87 @@ func TestNewStore(t *testing.T) { require.NoError(t, os.Mkdir(filepath.Join(tempDir, "config"), 0700)) t.Run("database dsn", func(t *testing.T) { - ds, err := config.NewStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource), false, nil) + ds, err := config.NewStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource), false, false, nil) require.NoError(t, err) ds.Close() }) t.Run("database dsn, watch ignored", func(t *testing.T) { - ds, err := config.NewStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource), true, nil) + ds, err := config.NewStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource), true, false, nil) require.NoError(t, err) ds.Close() }) t.Run("file dsn", func(t *testing.T) { - fs, err := config.NewStore("config.json", false, nil) + fs, err := config.NewStore("config.json", false, false, nil) require.NoError(t, err) fs.Close() }) t.Run("file dsn, watch", func(t *testing.T) { - fs, err := config.NewStore("config.json", true, nil) + fs, err := config.NewStore("config.json", true, false, nil) require.NoError(t, err) fs.Close() }) } + +func TestNewStoreReadOnly(t *testing.T) { + if testing.Short() { + t.SkipNow() + } + sqlSettings := mainHelper.GetSQLSettings() + + tempDir, err := ioutil.TempDir("", "TestNewStore") + require.NoError(t, err) + + err = os.Chdir(tempDir) + require.NoError(t, err) + + require.NoError(t, os.Mkdir(filepath.Join(tempDir, "config"), 0700)) + + t.Run("database dsn", func(t *testing.T) { + ds, err := config.NewStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource), false, true, nil) + require.NoError(t, err) + + t.Run("Set", func(t *testing.T) { + cfg, err := ds.Set(emptyConfig) + require.Nil(t, cfg) + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + t.Run("SetFile", func(t *testing.T) { + err := ds.SetFile("config.json", []byte{}) + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + t.Run("RemoveFile", func(t *testing.T) { + err := ds.RemoveFile("config.json") + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + ds.Close() + }) + + t.Run("file dsn", func(t *testing.T) { + fs, err := config.NewStore("config.json", false, true, nil) + require.NoError(t, err) + + t.Run("Set", func(t *testing.T) { + cfg, err := fs.Set(emptyConfig) + require.Nil(t, cfg) + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + t.Run("SetFile", func(t *testing.T) { + err := fs.SetFile("config.json", []byte{}) + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + t.Run("RemoveFile", func(t *testing.T) { + err := fs.RemoveFile("config.json") + require.Equal(t, config.ErrReadOnlyStore, err) + }) + + fs.Close() + }) +} diff --git a/services/mailservice/mail_test.go b/services/mailservice/mail_test.go index 64515ebe03..ee4c027cf6 100644 --- a/services/mailservice/mail_test.go +++ b/services/mailservice/mail_test.go @@ -129,7 +129,7 @@ func TestSendMailUsingConfig(t *testing.T) { fsInner, err := config.NewFileStore("config.json", false) require.Nil(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.Nil(t, err) cfg := fs.Get() @@ -170,7 +170,7 @@ func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) { fsInner, err := config.NewFileStore("config.json", false) require.Nil(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.Nil(t, err) cfg := fs.Get() @@ -217,7 +217,7 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { fsInner, err := config.NewFileStore("config.json", false) require.Nil(t, err) - fs, err := config.NewStoreFromBacking(fsInner, nil) + fs, err := config.NewStoreFromBacking(fsInner, nil, false) require.Nil(t, err) cfg := fs.Get()