github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/config/utils.go (about)

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