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 }