From f2fcf3d839f4aa58df8f9968b5eb10e952171129 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 31 Aug 2023 14:36:24 -0400 Subject: [PATCH] MM-53107 Add setting to limit the max number of markdown nodes (#24414) * MM-53107 Add setting to limit the max number of markdown nodes * Add extra validation for a couple config fields * Add new setting to telemetry --- server/config/client.go | 1 + server/i18n/en.json | 8 +++++++ .../platform/services/telemetry/telemetry.go | 1 + server/public/model/config.go | 23 ++++++++++++++++++- .../admin_console/admin_definition.jsx | 9 ++++++++ .../admin_console/schema_admin_settings.jsx | 2 +- webapp/channels/src/i18n/en.json | 2 ++ 7 files changed, 44 insertions(+), 2 deletions(-) diff --git a/server/config/client.go b/server/config/client.go index 9f5c40a5b3..96b8586ea7 100644 --- a/server/config/client.go +++ b/server/config/client.go @@ -130,6 +130,7 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["DataRetentionFileRetentionDays"] = "0" props["CustomUrlSchemes"] = strings.Join(c.DisplaySettings.CustomURLSchemes, ",") + props["MaxMarkdownNodes"] = strconv.FormatInt(int64(*c.DisplaySettings.MaxMarkdownNodes), 10) props["IsDefaultMarketplace"] = strconv.FormatBool(*c.PluginSettings.MarketplaceURL == model.PluginSettingsDefaultMarketplaceURL) props["ExperimentalSharedChannels"] = "false" props["CollapsedThreads"] = *c.ServiceSettings.CollapsedThreads diff --git a/server/i18n/en.json b/server/i18n/en.json index c5e0e60fe9..9df84f5069 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -8643,6 +8643,10 @@ "id": "model.config.is_valid.ldap_username", "translation": "AD/LDAP field \"Username Attribute\" is required." }, + { + "id": "model.config.is_valid.link_metadata_timeout.app_error", + "translation": "Invalid value for link metadata timeout. Must be a positive number." + }, { "id": "model.config.is_valid.listen_address.app_error", "translation": "Invalid listen address for service settings Must be set." @@ -8863,6 +8867,10 @@ "id": "model.config.is_valid.tls_overwrite_cipher.app_error", "translation": "Invalid value passed for TLS overwrite cipher - Please refer to the documentation for valid values." }, + { + "id": "model.config.is_valid.user_status_away_timeout.app_error", + "translation": "Invalid value for user status away timeout. Must be a positive number." + }, { "id": "model.config.is_valid.webserver_security.app_error", "translation": "Invalid value for webserver connection security." diff --git a/server/platform/services/telemetry/telemetry.go b/server/platform/services/telemetry/telemetry.go index ef8c485874..f84289a29e 100644 --- a/server/platform/services/telemetry/telemetry.go +++ b/server/platform/services/telemetry/telemetry.go @@ -855,6 +855,7 @@ func (ts *TelemetryService) trackConfig() { ts.SendTelemetry(TrackConfigDisplay, map[string]any{ "experimental_timezone": *cfg.DisplaySettings.ExperimentalTimezone, "isdefault_custom_url_schemes": len(cfg.DisplaySettings.CustomURLSchemes) != 0, + "isdefault_max_markdown_nodes": isDefault(*cfg.DisplaySettings.MaxMarkdownNodes, 0), }) ts.SendTelemetry(TrackConfigGuestAccounts, map[string]any{ diff --git a/server/public/model/config.go b/server/public/model/config.go index a94208e6b0..15de38c092 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -3149,7 +3149,8 @@ func (s *MessageExportSettings) SetDefaults() { } type DisplaySettings struct { - CustomURLSchemes []string `access:"site_customization"` + CustomURLSchemes []string `access:"site_posts"` + MaxMarkdownNodes *int `access:"site_posts"` ExperimentalTimezone *bool `access:"experimental_features"` } @@ -3159,6 +3160,10 @@ func (s *DisplaySettings) SetDefaults() { s.CustomURLSchemes = customURLSchemes } + if s.MaxMarkdownNodes == nil { + s.MaxMarkdownNodes = NewInt(0) + } + if s.ExperimentalTimezone == nil { s.ExperimentalTimezone = NewBool(true) } @@ -3502,6 +3507,10 @@ func (o *Config) IsValid() *AppError { return appErr } + if appErr := o.ExperimentalSettings.isValid(); appErr != nil { + return appErr + } + if appErr := o.SqlSettings.isValid(); appErr != nil { return appErr } @@ -3577,6 +3586,10 @@ func (s *TeamSettings) isValid() *AppError { return NewAppError("Config.IsValid", "model.config.is_valid.max_channels.app_error", nil, "", http.StatusBadRequest) } + if *s.UserStatusAwayTimeout <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.user_status_away_timeout.app_error", nil, "", http.StatusBadRequest) + } + if *s.MaxNotificationsPerChannel <= 0 { return NewAppError("Config.IsValid", "model.config.is_valid.max_notify_per_channel.app_error", nil, "", http.StatusBadRequest) } @@ -3596,6 +3609,14 @@ func (s *TeamSettings) isValid() *AppError { return nil } +func (s *ExperimentalSettings) isValid() *AppError { + if *s.LinkMetadataTimeoutMilliseconds <= 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.link_metadata_timeout.app_error", nil, "", http.StatusBadRequest) + } + + return nil +} + func (s *SqlSettings) isValid() *AppError { if *s.AtRestEncryptKey != "" && len(*s.AtRestEncryptKey) < 32 { return NewAppError("Config.IsValid", "model.config.is_valid.encrypt_sql.app_error", nil, "", http.StatusBadRequest) diff --git a/webapp/channels/src/components/admin_console/admin_definition.jsx b/webapp/channels/src/components/admin_console/admin_definition.jsx index def880e2d6..ca3de04e91 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.jsx +++ b/webapp/channels/src/components/admin_console/admin_definition.jsx @@ -3056,6 +3056,15 @@ const AdminDefinition = { key: 'DisplaySettings.CustomURLSchemes', isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.POSTS)), }, + { + type: Constants.SettingsTypes.TYPE_NUMBER, + key: 'DisplaySettings.MaxMarkdownNodes', + label: t('admin.customization.maxMarkdownNodesTitle'), + label_default: 'Max Markdown Nodes:', + help_text: t('admin.customization.maxMarkdownNodesDesc'), + help_text_default: 'When rendering Markdown text in the mobile app, controls the maximum number of Markdown elements (eg. emojis, links, table cells, etc) that can be in a single piece of text. If set to 0, a default limit will be used.', + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.POSTS)), + }, { type: Constants.SettingsTypes.TYPE_TEXT, key: 'ServiceSettings.GoogleDeveloperKey', diff --git a/webapp/channels/src/components/admin_console/schema_admin_settings.jsx b/webapp/channels/src/components/admin_console/schema_admin_settings.jsx index 7bbd973f63..23ea2ea71d 100644 --- a/webapp/channels/src/components/admin_console/schema_admin_settings.jsx +++ b/webapp/channels/src/components/admin_console/schema_admin_settings.jsx @@ -447,7 +447,7 @@ export default class SchemaAdminSettings extends React.PureComponent { inputType = 'textarea'; } - let value = this.state[setting.key] || ''; + let value = this.state[setting.key] ?? ''; if (setting.dynamic_value) { value = setting.dynamic_value(value, this.props.config, this.state, this.props.license); } diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 24672e7385..4dbe30c0b0 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -667,6 +667,8 @@ "admin.customization.enableSVGsTitle": "Enable SVGs:", "admin.customization.iosAppDownloadLinkDesc": "Add a link to download the iOS app. Users who access the site on a mobile web browser will be prompted with a page giving them the option to download the app. Leave this field blank to prevent the page from appearing.", "admin.customization.iosAppDownloadLinkTitle": "iOS App Download Link:", + "admin.customization.maxMarkdownNodesDesc": "When rendering Markdown text in the mobile app, controls the maximum number of Markdown elements (eg. emojis, links, table cells, etc) that can be in a single piece of text. If set to 0, a default limit will be used.", + "admin.customization.maxMarkdownNodesTitle": "Maximum Markdown Nodes:", "admin.customization.restrictLinkPreviewsDesc": "Link previews and image link previews will not be shown for the above list of comma-separated domains.", "admin.customization.restrictLinkPreviewsExample": "E.g.: \"internal.mycompany.com, images.example.com\"", "admin.customization.restrictLinkPreviewsTitle": "Disable website link previews from these domains:",