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