github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/config/utils.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package config 5 6 import ( 7 "encoding/json" 8 "reflect" 9 "strings" 10 11 "github.com/mattermost/mattermost-server/v5/mlog" 12 "github.com/mattermost/mattermost-server/v5/model" 13 "github.com/mattermost/mattermost-server/v5/utils" 14 ) 15 16 // desanitize replaces fake settings with their actual values. 17 func desanitize(actual, target *model.Config) { 18 if target.LdapSettings.BindPassword != nil && *target.LdapSettings.BindPassword == model.FAKE_SETTING { 19 *target.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword 20 } 21 22 if *target.FileSettings.PublicLinkSalt == model.FAKE_SETTING { 23 *target.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt 24 } 25 if *target.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING { 26 target.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey 27 } 28 29 if *target.EmailSettings.SMTPPassword == model.FAKE_SETTING { 30 target.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword 31 } 32 33 if *target.GitLabSettings.Secret == model.FAKE_SETTING { 34 target.GitLabSettings.Secret = actual.GitLabSettings.Secret 35 } 36 37 if target.GoogleSettings.Secret != nil && *target.GoogleSettings.Secret == model.FAKE_SETTING { 38 target.GoogleSettings.Secret = actual.GoogleSettings.Secret 39 } 40 41 if target.Office365Settings.Secret != nil && *target.Office365Settings.Secret == model.FAKE_SETTING { 42 target.Office365Settings.Secret = actual.Office365Settings.Secret 43 } 44 45 if target.OpenIdSettings.Secret != nil && *target.OpenIdSettings.Secret == model.FAKE_SETTING { 46 target.OpenIdSettings.Secret = actual.OpenIdSettings.Secret 47 } 48 49 if *target.SqlSettings.DataSource == model.FAKE_SETTING { 50 *target.SqlSettings.DataSource = *actual.SqlSettings.DataSource 51 } 52 if *target.SqlSettings.AtRestEncryptKey == model.FAKE_SETTING { 53 target.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey 54 } 55 56 if *target.ElasticsearchSettings.Password == model.FAKE_SETTING { 57 *target.ElasticsearchSettings.Password = *actual.ElasticsearchSettings.Password 58 } 59 60 if len(target.SqlSettings.DataSourceReplicas) == len(actual.SqlSettings.DataSourceReplicas) { 61 for i, value := range target.SqlSettings.DataSourceReplicas { 62 if value == model.FAKE_SETTING { 63 target.SqlSettings.DataSourceReplicas[i] = actual.SqlSettings.DataSourceReplicas[i] 64 } 65 } 66 } 67 68 if len(target.SqlSettings.DataSourceSearchReplicas) == len(actual.SqlSettings.DataSourceSearchReplicas) { 69 for i, value := range target.SqlSettings.DataSourceSearchReplicas { 70 if value == model.FAKE_SETTING { 71 target.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i] 72 } 73 } 74 } 75 76 if *target.MessageExportSettings.GlobalRelaySettings.SmtpPassword == model.FAKE_SETTING { 77 *target.MessageExportSettings.GlobalRelaySettings.SmtpPassword = *actual.MessageExportSettings.GlobalRelaySettings.SmtpPassword 78 } 79 80 if target.ServiceSettings.GfycatApiSecret != nil && *target.ServiceSettings.GfycatApiSecret == model.FAKE_SETTING { 81 *target.ServiceSettings.GfycatApiSecret = *actual.ServiceSettings.GfycatApiSecret 82 } 83 84 if *target.ServiceSettings.SplitKey == model.FAKE_SETTING { 85 *target.ServiceSettings.SplitKey = *actual.ServiceSettings.SplitKey 86 } 87 } 88 89 // fixConfig patches invalid or missing data in the configuration. 90 func fixConfig(cfg *model.Config) { 91 // Ensure SiteURL has no trailing slash. 92 if strings.HasSuffix(*cfg.ServiceSettings.SiteURL, "/") { 93 *cfg.ServiceSettings.SiteURL = strings.TrimRight(*cfg.ServiceSettings.SiteURL, "/") 94 } 95 96 // Ensure the directory for a local file store has a trailing slash. 97 if *cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL { 98 if *cfg.FileSettings.Directory != "" && !strings.HasSuffix(*cfg.FileSettings.Directory, "/") { 99 *cfg.FileSettings.Directory += "/" 100 } 101 } 102 103 FixInvalidLocales(cfg) 104 } 105 106 // FixInvalidLocales checks and corrects the given config for invalid locale-related settings. 107 // 108 // Ideally, this function would be completely internal, but it's currently exposed to allow the cli 109 // to test the config change before allowing the save. 110 func FixInvalidLocales(cfg *model.Config) bool { 111 var changed bool 112 113 locales := utils.GetSupportedLocales() 114 if _, ok := locales[*cfg.LocalizationSettings.DefaultServerLocale]; !ok { 115 *cfg.LocalizationSettings.DefaultServerLocale = model.DEFAULT_LOCALE 116 mlog.Warn("DefaultServerLocale must be one of the supported locales. Setting DefaultServerLocale to en as default value.") 117 changed = true 118 } 119 120 if _, ok := locales[*cfg.LocalizationSettings.DefaultClientLocale]; !ok { 121 *cfg.LocalizationSettings.DefaultClientLocale = model.DEFAULT_LOCALE 122 mlog.Warn("DefaultClientLocale must be one of the supported locales. Setting DefaultClientLocale to en as default value.") 123 changed = true 124 } 125 126 if *cfg.LocalizationSettings.AvailableLocales != "" { 127 isDefaultClientLocaleInAvailableLocales := false 128 for _, word := range strings.Split(*cfg.LocalizationSettings.AvailableLocales, ",") { 129 if _, ok := locales[word]; !ok { 130 *cfg.LocalizationSettings.AvailableLocales = "" 131 isDefaultClientLocaleInAvailableLocales = true 132 mlog.Warn("AvailableLocales must include DefaultClientLocale. Setting AvailableLocales to all locales as default value.") 133 changed = true 134 break 135 } 136 137 if word == *cfg.LocalizationSettings.DefaultClientLocale { 138 isDefaultClientLocaleInAvailableLocales = true 139 } 140 } 141 142 availableLocales := *cfg.LocalizationSettings.AvailableLocales 143 144 if !isDefaultClientLocaleInAvailableLocales { 145 availableLocales += "," + *cfg.LocalizationSettings.DefaultClientLocale 146 mlog.Warn("Adding DefaultClientLocale to AvailableLocales.") 147 changed = true 148 } 149 150 *cfg.LocalizationSettings.AvailableLocales = strings.Join(utils.RemoveDuplicatesFromStringArray(strings.Split(availableLocales, ",")), ",") 151 } 152 153 return changed 154 } 155 156 // Merge merges two configs together. The receiver's values are overwritten with the patch's 157 // values except when the patch's values are nil. 158 func Merge(cfg *model.Config, patch *model.Config, mergeConfig *utils.MergeConfig) (*model.Config, error) { 159 ret, err := utils.Merge(cfg, patch, mergeConfig) 160 if err != nil { 161 return nil, err 162 } 163 164 retCfg := ret.(model.Config) 165 return &retCfg, nil 166 } 167 168 func IsDatabaseDSN(dsn string) bool { 169 return strings.HasPrefix(dsn, "mysql://") || strings.HasPrefix(dsn, "postgres://") 170 } 171 172 // stripPassword remove the password from a given DSN 173 func stripPassword(dsn, schema string) string { 174 prefix := schema + "://" 175 dsn = strings.TrimPrefix(dsn, prefix) 176 177 i := strings.Index(dsn, ":") 178 j := strings.LastIndex(dsn, "@") 179 180 // Return error if no @ sign is found 181 if j < 0 { 182 return "(omitted due to error parsing the DSN)" 183 } 184 185 // Return back the input if no password is found 186 if i < 0 || i > j { 187 return prefix + dsn 188 } 189 190 return prefix + dsn[:i+1] + dsn[j:] 191 } 192 193 func IsJsonMap(data string) bool { 194 var m map[string]interface{} 195 return json.Unmarshal([]byte(data), &m) == nil 196 } 197 198 func JSONToLogTargetCfg(data []byte) (mlog.LogTargetCfg, error) { 199 cfg := make(mlog.LogTargetCfg) 200 err := json.Unmarshal(data, &cfg) 201 if err != nil { 202 return nil, err 203 } 204 return cfg, nil 205 } 206 207 func GetValueByPath(path []string, obj interface{}) (interface{}, bool) { 208 r := reflect.ValueOf(obj) 209 var val reflect.Value 210 if r.Kind() == reflect.Map { 211 val = r.MapIndex(reflect.ValueOf(path[0])) 212 if val.IsValid() { 213 val = val.Elem() 214 } 215 } else { 216 val = r.FieldByName(path[0]) 217 } 218 219 if !val.IsValid() { 220 return nil, false 221 } 222 223 switch { 224 case len(path) == 1: 225 return val.Interface(), true 226 case val.Kind() == reflect.Struct: 227 return GetValueByPath(path[1:], val.Interface()) 228 case val.Kind() == reflect.Map: 229 remainingPath := strings.Join(path[1:], ".") 230 mapIter := val.MapRange() 231 for mapIter.Next() { 232 key := mapIter.Key().String() 233 if strings.HasPrefix(remainingPath, key) { 234 i := strings.Count(key, ".") + 2 // number of dots + a dot on each side 235 mapVal := mapIter.Value() 236 // if no sub field path specified, return the object 237 if len(path[i:]) == 0 { 238 return mapVal.Interface(), true 239 } 240 data := mapVal.Interface() 241 if mapVal.Kind() == reflect.Ptr { 242 data = mapVal.Elem().Interface() // if value is a pointer, dereference it 243 } 244 // pass subpath 245 return GetValueByPath(path[i:], data) 246 } 247 } 248 } 249 return nil, false 250 }