github.com/mattermost/mattermost-server/server/v8@v8.0.0-20230610055354-a6d1d38b273d/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/server/public/model"
    14  	"github.com/mattermost/mattermost-server/server/public/shared/i18n"
    15  	"github.com/mattermost/mattermost-server/server/public/shared/mlog"
    16  	"github.com/mattermost/mattermost-server/server/v8/channels/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.FakeSetting {
    27  		*target.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword
    28  	}
    29  
    30  	if *target.FileSettings.PublicLinkSalt == model.FakeSetting {
    31  		*target.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt
    32  	}
    33  	if *target.FileSettings.AmazonS3SecretAccessKey == model.FakeSetting {
    34  		target.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey
    35  	}
    36  
    37  	if *target.EmailSettings.SMTPPassword == model.FakeSetting {
    38  		target.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword
    39  	}
    40  
    41  	if *target.GitLabSettings.Secret == model.FakeSetting {
    42  		target.GitLabSettings.Secret = actual.GitLabSettings.Secret
    43  	}
    44  
    45  	if target.GoogleSettings.Secret != nil && *target.GoogleSettings.Secret == model.FakeSetting {
    46  		target.GoogleSettings.Secret = actual.GoogleSettings.Secret
    47  	}
    48  
    49  	if target.Office365Settings.Secret != nil && *target.Office365Settings.Secret == model.FakeSetting {
    50  		target.Office365Settings.Secret = actual.Office365Settings.Secret
    51  	}
    52  
    53  	if target.OpenIdSettings.Secret != nil && *target.OpenIdSettings.Secret == model.FakeSetting {
    54  		target.OpenIdSettings.Secret = actual.OpenIdSettings.Secret
    55  	}
    56  
    57  	if *target.SqlSettings.DataSource == model.FakeSetting {
    58  		*target.SqlSettings.DataSource = *actual.SqlSettings.DataSource
    59  	}
    60  	if *target.SqlSettings.AtRestEncryptKey == model.FakeSetting {
    61  		target.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey
    62  	}
    63  
    64  	if *target.ElasticsearchSettings.Password == model.FakeSetting {
    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.FakeSetting {
    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.FakeSetting {
    79  				target.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i]
    80  			}
    81  		}
    82  	}
    83  
    84  	if *target.MessageExportSettings.GlobalRelaySettings.SMTPPassword == model.FakeSetting {
    85  		*target.MessageExportSettings.GlobalRelaySettings.SMTPPassword = *actual.MessageExportSettings.GlobalRelaySettings.SMTPPassword
    86  	}
    87  
    88  	if target.ServiceSettings.GfycatAPISecret != nil && *target.ServiceSettings.GfycatAPISecret == model.FakeSetting {
    89  		*target.ServiceSettings.GfycatAPISecret = *actual.ServiceSettings.GfycatAPISecret
    90  	}
    91  
    92  	if *target.ServiceSettings.SplitKey == model.FakeSetting {
    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.ImageDriverLocal {
   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.DefaultLocale
   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.DefaultLocale
   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://") ||
   178  		strings.HasPrefix(dsn, "postgres://") ||
   179  		strings.HasPrefix(dsn, "postgresql://")
   180  }
   181  
   182  func isJSONMap(data []byte) bool {
   183  	var m map[string]any
   184  	err := json.Unmarshal(data, &m)
   185  	return err == nil
   186  }
   187  
   188  func GetValueByPath(path []string, obj any) (any, bool) {
   189  	r := reflect.ValueOf(obj)
   190  	var val reflect.Value
   191  	if r.Kind() == reflect.Map {
   192  		val = r.MapIndex(reflect.ValueOf(path[0]))
   193  		if val.IsValid() {
   194  			val = val.Elem()
   195  		}
   196  	} else {
   197  		val = r.FieldByName(path[0])
   198  	}
   199  
   200  	if !val.IsValid() {
   201  		return nil, false
   202  	}
   203  
   204  	switch {
   205  	case len(path) == 1:
   206  		return val.Interface(), true
   207  	case val.Kind() == reflect.Struct:
   208  		return GetValueByPath(path[1:], val.Interface())
   209  	case val.Kind() == reflect.Map:
   210  		remainingPath := strings.Join(path[1:], ".")
   211  		mapIter := val.MapRange()
   212  		for mapIter.Next() {
   213  			key := mapIter.Key().String()
   214  			if strings.HasPrefix(remainingPath, key) {
   215  				i := strings.Count(key, ".") + 2 // number of dots + a dot on each side
   216  				mapVal := mapIter.Value()
   217  				// if no sub field path specified, return the object
   218  				if len(path[i:]) == 0 {
   219  					return mapVal.Interface(), true
   220  				}
   221  				data := mapVal.Interface()
   222  				if mapVal.Kind() == reflect.Ptr {
   223  					data = mapVal.Elem().Interface() // if value is a pointer, dereference it
   224  				}
   225  				// pass subpath
   226  				return GetValueByPath(path[i:], data)
   227  			}
   228  		}
   229  	}
   230  	return nil, false
   231  }
   232  
   233  func equal(oldCfg, newCfg *model.Config) (bool, error) {
   234  	oldCfgBytes, err := json.Marshal(oldCfg)
   235  	if err != nil {
   236  		return false, fmt.Errorf("failed to marshal old config: %w", err)
   237  	}
   238  	newCfgBytes, err := json.Marshal(newCfg)
   239  	if err != nil {
   240  		return false, fmt.Errorf("failed to marshal new config: %w", err)
   241  	}
   242  	return !bytes.Equal(oldCfgBytes, newCfgBytes), nil
   243  }