From 2fe88787492077af294ee82a8775304f655c8805 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Wed, 30 May 2018 10:48:04 -0400 Subject: MM-9547 Added config setting to control url autolinking schemes (#8862) * MM-9547 Added config setting to control autolinking schemes * Renamed AutolinkingSchemes to CustomUrlSchemes --- config/default.json | 1 + i18n/en.json | 4 +++ model/config.go | 31 ++++++++++++++++++++ model/config_test.go | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++ utils/config.go | 1 + 5 files changed, 120 insertions(+) diff --git a/config/default.json b/config/default.json index 3548339d0..9f35ad7d1 100644 --- a/config/default.json +++ b/config/default.json @@ -97,6 +97,7 @@ "ExperimentalPrimaryTeam": "" }, "DisplaySettings": { + "CustomUrlSchemes": [], "ExperimentalTimezone": false }, "ClientRequirements": { diff --git a/i18n/en.json b/i18n/en.json index 4a9532ca3..fb6d5eeb1 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -4950,6 +4950,10 @@ "id": "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", "translation": "Message retention must be one day or longer." }, + { + "id": "model.config.is_valid.display.custom_url_schemes.app_error", + "translation": "The custom URL scheme {{.Scheme}} is invalid. Custom URL schemes must start with a letter and contain only letters, numbers, plus (+), period (.), and hyphen (-)." + }, { "id": "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", "translation": "Elasticsearch AggregatePostsAfterDays setting must be a number greater than or equal to 1" diff --git a/model/config.go b/model/config.go index d5a8ad38e..074632a67 100644 --- a/model/config.go +++ b/model/config.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "regexp" "strings" "time" ) @@ -1772,10 +1773,16 @@ func (s *MessageExportSettings) SetDefaults() { } type DisplaySettings struct { + CustomUrlSchemes *[]string ExperimentalTimezone *bool } func (s *DisplaySettings) SetDefaults() { + if s.CustomUrlSchemes == nil { + customUrlSchemes := []string{} + s.CustomUrlSchemes = &customUrlSchemes + } + if s.ExperimentalTimezone == nil { s.ExperimentalTimezone = NewBool(false) } @@ -1968,6 +1975,10 @@ func (o *Config) IsValid() *AppError { return err } + if err := o.DisplaySettings.isValid(); err != nil { + return err + } + return nil } @@ -2348,6 +2359,26 @@ func (mes *MessageExportSettings) isValid(fs FileSettings) *AppError { return nil } +func (ds *DisplaySettings) isValid() *AppError { + if len(*ds.CustomUrlSchemes) != 0 { + validProtocolPattern := regexp.MustCompile(`(?i)^\s*[a-z][a-z0-9+.-]*\s*$`) + + for _, scheme := range *ds.CustomUrlSchemes { + if !validProtocolPattern.MatchString(scheme) { + return NewAppError( + "Config.IsValid", + "model.config.is_valid.display.custom_url_schemes.app_error", + map[string]interface{}{"Protocol": scheme}, + "", + http.StatusBadRequest, + ) + } + } + } + + return nil +} + func (o *Config) GetSanitizeOptions() map[string]bool { options := map[string]bool{} options["fullname"] = o.PrivacySettings.ShowFullName diff --git a/model/config_test.go b/model/config_test.go index b7533145b..5406d680d 100644 --- a/model/config_test.go +++ b/model/config_test.go @@ -436,3 +436,86 @@ func TestMessageExportSetDefaultsExportDisabledExportFromTimestampNonZero(t *tes require.Equal(t, int64(0), *mes.ExportFromTimestamp) require.Equal(t, 10000, *mes.BatchSize) } + +func TestDisplaySettingsIsValidCustomUrlSchemes(t *testing.T) { + tests := []struct { + name string + value []string + valid bool + }{ + { + name: "empty", + value: []string{}, + valid: true, + }, + { + name: "custom protocol", + value: []string{"steam"}, + valid: true, + }, + { + name: "multiple custom protocols", + value: []string{"bitcoin", "rss", "redis"}, + valid: true, + }, + { + name: "containing numbers", + value: []string{"ut2004", "ts3server", "h323"}, + valid: true, + }, + { + name: "containing period", + value: []string{"iris.beep"}, + valid: true, + }, + { + name: "containing hyphen", + value: []string{"ms-excel"}, + valid: true, + }, + { + name: "containing plus", + value: []string{"coap+tcp", "coap+ws"}, + valid: true, + }, + { + name: "starting with number", + value: []string{"4four"}, + valid: false, + }, + { + name: "starting with period", + value: []string{"data", ".dot"}, + valid: false, + }, + { + name: "starting with hyphen", + value: []string{"-hyphen", "dns"}, + valid: false, + }, + { + name: "invalid symbols", + value: []string{"!!fun!!"}, + valid: false, + }, + { + name: "invalid letters", + value: []string{"école"}, + valid: false, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ds := &DisplaySettings{} + ds.SetDefaults() + + ds.CustomUrlSchemes = &test.value + + if err := ds.isValid(); err != nil && test.valid { + t.Error("Expected CustomUrlSchemes to be valid but got error:", err) + } else if err == nil && !test.valid { + t.Error("Expected CustomUrlSchemes to be invalid but got no error") + } + }) + } +} diff --git a/utils/config.go b/utils/config.go index 00fd2642a..2e6f4182f 100644 --- a/utils/config.go +++ b/utils/config.go @@ -609,6 +609,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["PasswordRequireUppercase"] = strconv.FormatBool(*c.PasswordSettings.Uppercase) props["PasswordRequireNumber"] = strconv.FormatBool(*c.PasswordSettings.Number) props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol) + props["CustomUrlSchemes"] = strings.Join(*c.DisplaySettings.CustomUrlSchemes, ",") if license != nil { props["ExperimentalHideTownSquareinLHS"] = strconv.FormatBool(*c.TeamSettings.ExperimentalHideTownSquareinLHS) -- cgit v1.2.3-1-g7c22