[MM-19720] Expose endpoint for config patch (#12997)

Этот коммит содержится в:
Rajat Varyani
2020-01-14 17:36:17 +05:30
коммит произвёл Ben Schumacher
родитель 69f4dcd955
Коммит 51a61f6bc1
4 изменённых файлов: 180 добавлений и 13 удалений

Просмотреть файл

@@ -15,6 +15,7 @@ import (
func (api *API) InitConfig() {
api.BaseRoutes.ApiRoot.Handle("/config", api.ApiSessionRequired(getConfig)).Methods("GET")
api.BaseRoutes.ApiRoot.Handle("/config", api.ApiSessionRequired(updateConfig)).Methods("PUT")
api.BaseRoutes.ApiRoot.Handle("/config/patch", api.ApiSessionRequired(patchConfig)).Methods("PUT")
api.BaseRoutes.ApiRoot.Handle("/config/reload", api.ApiSessionRequired(configReload)).Methods("POST")
api.BaseRoutes.ApiRoot.Handle("/config/client", api.ApiHandler(getClientConfig)).Methods("GET")
api.BaseRoutes.ApiRoot.Handle("/config/environment", api.ApiSessionRequired(getEnvironmentConfig)).Methods("GET")
@@ -81,19 +82,7 @@ func updateConfig(c *Context, w http.ResponseWriter, r *http.Request) {
// Do not allow plugin uploads to be toggled through the API
cfg.PluginSettings.EnableUploads = appCfg.PluginSettings.EnableUploads
// If the Message Export feature has been toggled in the System Console, rewrite the ExportFromTimestamp field to an
// appropriate value. The rewriting occurs here to ensure it doesn't affect values written to the config file
// directly and not through the System Console UI.
if *cfg.MessageExportSettings.EnableExport != *appCfg.MessageExportSettings.EnableExport {
if *cfg.MessageExportSettings.EnableExport && *cfg.MessageExportSettings.ExportFromTimestamp == int64(0) {
// When the feature is toggled on, use the current timestamp as the start time for future exports.
cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(model.GetMillis())
} else if !*cfg.MessageExportSettings.EnableExport {
// When the feature is disabled, reset the timestamp so that the timestamp will be set if
// the feature is re-enabled from the System Console in future.
cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(0)
}
}
c.App.HandleMessageExportConfig(cfg, appCfg)
err := cfg.IsValid()
if err != nil {
@@ -149,3 +138,59 @@ func getEnvironmentConfig(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write([]byte(model.StringInterfaceToJson(envConfig)))
}
func patchConfig(c *Context, w http.ResponseWriter, r *http.Request) {
cfg := model.ConfigFromJson(r.Body)
if cfg == nil {
c.SetInvalidParam("config")
return
}
if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) {
c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
return
}
appCfg := c.App.Config()
var filterFn utils.StructFieldFilter
if *appCfg.ExperimentalSettings.RestrictSystemAdmin {
filterFn = func(structField reflect.StructField, base, patch reflect.Value) bool {
return !(structField.Tag.Get("restricted") == "true")
}
} else {
filterFn = func(structField reflect.StructField, base, patch reflect.Value) bool {
return true
}
}
// Do not allow plugin uploads to be toggled through the API
cfg.PluginSettings.EnableUploads = appCfg.PluginSettings.EnableUploads
if cfg.MessageExportSettings.EnableExport != nil {
c.App.HandleMessageExportConfig(cfg, appCfg)
}
updatedCfg, mergeErr := config.Merge(appCfg, cfg, &utils.MergeConfig{
StructFieldFilter: filterFn,
})
if mergeErr != nil {
c.Err = model.NewAppError("patchConfig", "api.config.update_config.restricted_merge.app_error", nil, mergeErr.Error(), http.StatusInternalServerError)
return
}
err := updatedCfg.IsValid()
if err != nil {
c.Err = err
return
}
err = c.App.SaveConfig(updatedCfg, true)
if err != nil {
c.Err = err
return
}
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Write([]byte(c.App.GetSanitizedConfig().ToJson()))
}

Просмотреть файл

@@ -345,3 +345,100 @@ func TestGetOldClientConfig(t *testing.T) {
}
})
}
func TestPatchConfig(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
client := th.Client
t.Run("config is missing", func(t *testing.T) {
_, response := client.PatchConfig(nil)
CheckBadRequestStatus(t, response)
})
t.Run("user is not system admin", func(t *testing.T) {
_, response := client.PatchConfig(&model.Config{})
CheckForbiddenStatus(t, response)
})
t.Run("should not update the restricted fields when restrict toggle is on", func(t *testing.T) {
*th.App.Config().ExperimentalSettings.RestrictSystemAdmin = true
config := model.Config{LogSettings: model.LogSettings{
ConsoleLevel: model.NewString("INFO"),
}}
updatedConfig, _ := th.SystemAdminClient.PatchConfig(&config)
assert.Equal(t, "DEBUG", *updatedConfig.LogSettings.ConsoleLevel)
})
t.Run("check if config is valid", func(t *testing.T) {
config := model.Config{PasswordSettings: model.PasswordSettings{
MinimumLength: model.NewInt(4),
}}
_, response := th.SystemAdminClient.PatchConfig(&config)
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
assert.NotNil(t, response.Error)
assert.Equal(t, "model.config.is_valid.password_length.app_error", response.Error.Id)
})
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, _ := th.SystemAdminClient.GetConfig()
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.NewBool(false)}
config := model.Config{PasswordSettings: model.PasswordSettings{
Lowercase: model.NewBool(true),
MinimumLength: model.NewInt(15),
}, LogSettings: model.LogSettings{
ConsoleLevel: model.NewString("INFO"),
},
TeamSettings: model.TeamSettings{
ExperimentalDefaultChannels: []string{"another-channel"},
},
PluginSettings: model.PluginSettings{
PluginStates: states,
},
}
_, response := th.SystemAdminClient.PatchConfig(&config)
updatedConfig, _ := th.SystemAdminClient.GetConfig()
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"))
})
t.Run("should sanitize config", func(t *testing.T) {
config := model.Config{PasswordSettings: model.PasswordSettings{
Symbol: model.NewBool(true),
}}
updatedConfig, _ := th.SystemAdminClient.PatchConfig(&config)
assert.Equal(t, model.FAKE_SETTING, *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.NewBool(true),
}}
updatedConfig, _ := th.SystemAdminClient.PatchConfig(&config)
assert.Equal(t, false, *updatedConfig.PluginSettings.EnableUploads)
})
}