github.com/wgh-/mattermost-server@v4.8.0-rc2+incompatible/utils/config.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package utils 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "os" 12 "path" 13 "path/filepath" 14 "strconv" 15 "strings" 16 17 l4g "github.com/alecthomas/log4go" 18 "github.com/fsnotify/fsnotify" 19 "github.com/pkg/errors" 20 "github.com/spf13/viper" 21 22 "net/http" 23 24 "github.com/mattermost/mattermost-server/einterfaces" 25 "github.com/mattermost/mattermost-server/model" 26 ) 27 28 const ( 29 MODE_DEV = "dev" 30 MODE_BETA = "beta" 31 MODE_PROD = "prod" 32 LOG_ROTATE_SIZE = 10000 33 LOG_FILENAME = "mattermost.log" 34 ) 35 36 var originalDisableDebugLvl l4g.Level = l4g.DEBUG 37 38 // FindConfigFile attempts to find an existing configuration file. fileName can be an absolute or 39 // relative path or name such as "/opt/mattermost/config.json" or simply "config.json". An empty 40 // string is returned if no configuration is found. 41 func FindConfigFile(fileName string) (path string) { 42 if filepath.IsAbs(fileName) { 43 if _, err := os.Stat(fileName); err == nil { 44 return fileName 45 } 46 } else { 47 for _, dir := range []string{"./config", "../config", "../../config", "."} { 48 path, _ := filepath.Abs(filepath.Join(dir, fileName)) 49 if _, err := os.Stat(path); err == nil { 50 return path 51 } 52 } 53 } 54 return "" 55 } 56 57 func FindDir(dir string) (string, bool) { 58 fileName := "." 59 found := false 60 if _, err := os.Stat("./" + dir + "/"); err == nil { 61 fileName, _ = filepath.Abs("./" + dir + "/") 62 found = true 63 } else if _, err := os.Stat("../" + dir + "/"); err == nil { 64 fileName, _ = filepath.Abs("../" + dir + "/") 65 found = true 66 } else if _, err := os.Stat("../../" + dir + "/"); err == nil { 67 fileName, _ = filepath.Abs("../../" + dir + "/") 68 found = true 69 } 70 71 return fileName + "/", found 72 } 73 74 func DisableDebugLogForTest() { 75 if l4g.Global["stdout"] != nil { 76 originalDisableDebugLvl = l4g.Global["stdout"].Level 77 l4g.Global["stdout"].Level = l4g.ERROR 78 } 79 } 80 81 func EnableDebugLogForTest() { 82 if l4g.Global["stdout"] != nil { 83 l4g.Global["stdout"].Level = originalDisableDebugLvl 84 } 85 } 86 87 func ConfigureCmdLineLog() { 88 ls := model.LogSettings{} 89 ls.EnableConsole = true 90 ls.ConsoleLevel = "WARN" 91 ConfigureLog(&ls) 92 } 93 94 // TODO: this code initializes console and file logging. It will eventually be replaced by JSON logging in logger/logger.go 95 // See PLT-3893 for more information 96 func ConfigureLog(s *model.LogSettings) { 97 98 l4g.Close() 99 100 if s.EnableConsole { 101 level := l4g.DEBUG 102 if s.ConsoleLevel == "INFO" { 103 level = l4g.INFO 104 } else if s.ConsoleLevel == "WARN" { 105 level = l4g.WARNING 106 } else if s.ConsoleLevel == "ERROR" { 107 level = l4g.ERROR 108 } 109 110 lw := l4g.NewConsoleLogWriter() 111 lw.SetFormat("[%D %T] [%L] %M") 112 l4g.AddFilter("stdout", level, lw) 113 } 114 115 if s.EnableFile { 116 117 var fileFormat = s.FileFormat 118 119 if fileFormat == "" { 120 fileFormat = "[%D %T] [%L] %M" 121 } 122 123 level := l4g.DEBUG 124 if s.FileLevel == "INFO" { 125 level = l4g.INFO 126 } else if s.FileLevel == "WARN" { 127 level = l4g.WARNING 128 } else if s.FileLevel == "ERROR" { 129 level = l4g.ERROR 130 } 131 132 flw := l4g.NewFileLogWriter(GetLogFileLocation(s.FileLocation), false) 133 flw.SetFormat(fileFormat) 134 flw.SetRotate(true) 135 flw.SetRotateLines(LOG_ROTATE_SIZE) 136 l4g.AddFilter("file", level, flw) 137 } 138 } 139 140 func GetLogFileLocation(fileLocation string) string { 141 if fileLocation == "" { 142 logDir, _ := FindDir("logs") 143 return logDir + LOG_FILENAME 144 } else { 145 return path.Join(fileLocation, LOG_FILENAME) 146 } 147 } 148 149 func SaveConfig(fileName string, config *model.Config) *model.AppError { 150 b, err := json.MarshalIndent(config, "", " ") 151 if err != nil { 152 return model.NewAppError("SaveConfig", "utils.config.save_config.saving.app_error", 153 map[string]interface{}{"Filename": fileName}, err.Error(), http.StatusBadRequest) 154 } 155 156 err = ioutil.WriteFile(fileName, b, 0644) 157 if err != nil { 158 return model.NewAppError("SaveConfig", "utils.config.save_config.saving.app_error", 159 map[string]interface{}{"Filename": fileName}, err.Error(), http.StatusInternalServerError) 160 } 161 162 return nil 163 } 164 165 type ConfigWatcher struct { 166 watcher *fsnotify.Watcher 167 close chan struct{} 168 closed chan struct{} 169 } 170 171 func NewConfigWatcher(cfgFileName string, f func()) (*ConfigWatcher, error) { 172 watcher, err := fsnotify.NewWatcher() 173 if err != nil { 174 return nil, errors.Wrapf(err, "failed to create config watcher for file: "+cfgFileName) 175 } 176 177 configFile := filepath.Clean(cfgFileName) 178 configDir, _ := filepath.Split(configFile) 179 watcher.Add(configDir) 180 181 ret := &ConfigWatcher{ 182 watcher: watcher, 183 close: make(chan struct{}), 184 closed: make(chan struct{}), 185 } 186 187 go func() { 188 defer close(ret.closed) 189 defer watcher.Close() 190 191 for { 192 select { 193 case event := <-watcher.Events: 194 // we only care about the config file 195 if filepath.Clean(event.Name) == configFile { 196 if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { 197 l4g.Info(fmt.Sprintf("Config file watcher detected a change reloading %v", cfgFileName)) 198 199 if _, configReadErr := ReadConfigFile(cfgFileName, true); configReadErr == nil { 200 f() 201 } else { 202 l4g.Error(fmt.Sprintf("Failed to read while watching config file at %v with err=%v", cfgFileName, configReadErr.Error())) 203 } 204 } 205 } 206 case err := <-watcher.Errors: 207 l4g.Error(fmt.Sprintf("Failed while watching config file at %v with err=%v", cfgFileName, err.Error())) 208 case <-ret.close: 209 return 210 } 211 } 212 }() 213 214 return ret, nil 215 } 216 217 func (w *ConfigWatcher) Close() { 218 close(w.close) 219 <-w.closed 220 } 221 222 // ReadConfig reads and parses the given configuration. 223 func ReadConfig(r io.Reader, allowEnvironmentOverrides bool) (*model.Config, error) { 224 v := viper.New() 225 226 if allowEnvironmentOverrides { 227 v.SetEnvPrefix("mm") 228 v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) 229 v.AutomaticEnv() 230 } 231 232 v.SetConfigType("json") 233 if err := v.ReadConfig(r); err != nil { 234 return nil, err 235 } 236 237 var config model.Config 238 unmarshalErr := v.Unmarshal(&config) 239 if unmarshalErr == nil { 240 // https://github.com/spf13/viper/issues/324 241 // https://github.com/spf13/viper/issues/348 242 config.PluginSettings = model.PluginSettings{} 243 unmarshalErr = v.UnmarshalKey("pluginsettings", &config.PluginSettings) 244 } 245 return &config, unmarshalErr 246 } 247 248 // ReadConfigFile reads and parses the configuration at the given file path. 249 func ReadConfigFile(path string, allowEnvironmentOverrides bool) (*model.Config, error) { 250 f, err := os.Open(path) 251 if err != nil { 252 return nil, err 253 } 254 defer f.Close() 255 return ReadConfig(f, allowEnvironmentOverrides) 256 } 257 258 // EnsureConfigFile will attempt to locate a config file with the given name. If it does not exist, 259 // it will attempt to locate a default config file, and copy it to a file named fileName in the same 260 // directory. In either case, the config file path is returned. 261 func EnsureConfigFile(fileName string) (string, error) { 262 if configFile := FindConfigFile(fileName); configFile != "" { 263 return configFile, nil 264 } 265 if defaultPath := FindConfigFile("default.json"); defaultPath != "" { 266 destPath := filepath.Join(filepath.Dir(defaultPath), fileName) 267 src, err := os.Open(defaultPath) 268 if err != nil { 269 return "", err 270 } 271 defer src.Close() 272 dest, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 273 if err != nil { 274 return "", err 275 } 276 defer dest.Close() 277 if _, err := io.Copy(dest, src); err == nil { 278 return destPath, nil 279 } 280 } 281 return "", fmt.Errorf("no config file found") 282 } 283 284 // LoadConfig will try to search around for the corresponding config file. It will search 285 // /tmp/fileName then attempt ./config/fileName, then ../config/fileName and last it will look at 286 // fileName. 287 func LoadConfig(fileName string) (config *model.Config, configPath string, appErr *model.AppError) { 288 if fileName != filepath.Base(fileName) { 289 configPath = fileName 290 } else { 291 if path, err := EnsureConfigFile(fileName); err != nil { 292 appErr = model.NewAppError("LoadConfig", "utils.config.load_config.opening.panic", map[string]interface{}{"Filename": fileName, "Error": err.Error()}, "", 0) 293 return 294 } else { 295 configPath = path 296 } 297 } 298 299 config, err := ReadConfigFile(configPath, true) 300 if err != nil { 301 appErr = model.NewAppError("LoadConfig", "utils.config.load_config.decoding.panic", map[string]interface{}{"Filename": fileName, "Error": err.Error()}, "", 0) 302 return 303 } 304 305 needSave := len(config.SqlSettings.AtRestEncryptKey) == 0 || len(*config.FileSettings.PublicLinkSalt) == 0 || 306 len(config.EmailSettings.InviteSalt) == 0 307 308 config.SetDefaults() 309 310 if err := config.IsValid(); err != nil { 311 return nil, "", err 312 } 313 314 if needSave { 315 if err := SaveConfig(configPath, config); err != nil { 316 l4g.Warn(err.Error()) 317 } 318 } 319 320 if err := ValidateLocales(config); err != nil { 321 if err := SaveConfig(configPath, config); err != nil { 322 l4g.Warn(err.Error()) 323 } 324 } 325 326 if *config.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL { 327 dir := config.FileSettings.Directory 328 if len(dir) > 0 && dir[len(dir)-1:] != "/" { 329 config.FileSettings.Directory += "/" 330 } 331 } 332 333 return config, configPath, nil 334 } 335 336 func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.License) map[string]string { 337 props := make(map[string]string) 338 339 props["Version"] = model.CurrentVersion 340 props["BuildNumber"] = model.BuildNumber 341 props["BuildDate"] = model.BuildDate 342 props["BuildHash"] = model.BuildHash 343 props["BuildHashEnterprise"] = model.BuildHashEnterprise 344 props["BuildEnterpriseReady"] = model.BuildEnterpriseReady 345 346 props["SiteURL"] = strings.TrimRight(*c.ServiceSettings.SiteURL, "/") 347 props["WebsocketURL"] = strings.TrimRight(*c.ServiceSettings.WebsocketURL, "/") 348 props["SiteName"] = c.TeamSettings.SiteName 349 props["EnableAPIv3"] = strconv.FormatBool(*c.ServiceSettings.EnableAPIv3) 350 props["EnableTeamCreation"] = strconv.FormatBool(c.TeamSettings.EnableTeamCreation) 351 props["EnableUserCreation"] = strconv.FormatBool(c.TeamSettings.EnableUserCreation) 352 props["EnableOpenServer"] = strconv.FormatBool(*c.TeamSettings.EnableOpenServer) 353 props["RestrictDirectMessage"] = *c.TeamSettings.RestrictDirectMessage 354 props["RestrictTeamInvite"] = *c.TeamSettings.RestrictTeamInvite 355 props["RestrictPublicChannelCreation"] = *c.TeamSettings.RestrictPublicChannelCreation 356 props["RestrictPrivateChannelCreation"] = *c.TeamSettings.RestrictPrivateChannelCreation 357 props["RestrictPublicChannelManagement"] = *c.TeamSettings.RestrictPublicChannelManagement 358 props["RestrictPrivateChannelManagement"] = *c.TeamSettings.RestrictPrivateChannelManagement 359 props["RestrictPublicChannelDeletion"] = *c.TeamSettings.RestrictPublicChannelDeletion 360 props["RestrictPrivateChannelDeletion"] = *c.TeamSettings.RestrictPrivateChannelDeletion 361 props["RestrictPrivateChannelManageMembers"] = *c.TeamSettings.RestrictPrivateChannelManageMembers 362 props["EnableXToLeaveChannelsFromLHS"] = strconv.FormatBool(*c.TeamSettings.EnableXToLeaveChannelsFromLHS) 363 props["TeammateNameDisplay"] = *c.TeamSettings.TeammateNameDisplay 364 props["ExperimentalPrimaryTeam"] = *c.TeamSettings.ExperimentalPrimaryTeam 365 366 props["AndroidLatestVersion"] = c.ClientRequirements.AndroidLatestVersion 367 props["AndroidMinVersion"] = c.ClientRequirements.AndroidMinVersion 368 props["DesktopLatestVersion"] = c.ClientRequirements.DesktopLatestVersion 369 props["DesktopMinVersion"] = c.ClientRequirements.DesktopMinVersion 370 props["IosLatestVersion"] = c.ClientRequirements.IosLatestVersion 371 props["IosMinVersion"] = c.ClientRequirements.IosMinVersion 372 373 props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) 374 props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey 375 props["EnableIncomingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableIncomingWebhooks) 376 props["EnableOutgoingWebhooks"] = strconv.FormatBool(c.ServiceSettings.EnableOutgoingWebhooks) 377 props["EnableCommands"] = strconv.FormatBool(*c.ServiceSettings.EnableCommands) 378 props["EnableOnlyAdminIntegrations"] = strconv.FormatBool(*c.ServiceSettings.EnableOnlyAdminIntegrations) 379 props["EnablePostUsernameOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostUsernameOverride) 380 props["EnablePostIconOverride"] = strconv.FormatBool(c.ServiceSettings.EnablePostIconOverride) 381 props["EnableUserAccessTokens"] = strconv.FormatBool(*c.ServiceSettings.EnableUserAccessTokens) 382 props["EnableLinkPreviews"] = strconv.FormatBool(*c.ServiceSettings.EnableLinkPreviews) 383 props["EnableTesting"] = strconv.FormatBool(c.ServiceSettings.EnableTesting) 384 props["EnableDeveloper"] = strconv.FormatBool(*c.ServiceSettings.EnableDeveloper) 385 props["EnableDiagnostics"] = strconv.FormatBool(*c.LogSettings.EnableDiagnostics) 386 props["RestrictPostDelete"] = *c.ServiceSettings.RestrictPostDelete 387 props["AllowEditPost"] = *c.ServiceSettings.AllowEditPost 388 props["PostEditTimeLimit"] = fmt.Sprintf("%v", *c.ServiceSettings.PostEditTimeLimit) 389 props["CloseUnusedDirectMessages"] = strconv.FormatBool(*c.ServiceSettings.CloseUnusedDirectMessages) 390 props["EnablePreviewFeatures"] = strconv.FormatBool(*c.ServiceSettings.EnablePreviewFeatures) 391 props["EnableTutorial"] = strconv.FormatBool(*c.ServiceSettings.EnableTutorial) 392 props["ExperimentalEnableDefaultChannelLeaveJoinMessages"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages) 393 props["ExperimentalGroupUnreadChannels"] = *c.ServiceSettings.ExperimentalGroupUnreadChannels 394 395 props["SendEmailNotifications"] = strconv.FormatBool(c.EmailSettings.SendEmailNotifications) 396 props["SendPushNotifications"] = strconv.FormatBool(*c.EmailSettings.SendPushNotifications) 397 props["EnableSignUpWithEmail"] = strconv.FormatBool(c.EmailSettings.EnableSignUpWithEmail) 398 props["EnableSignInWithEmail"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithEmail) 399 props["EnableSignInWithUsername"] = strconv.FormatBool(*c.EmailSettings.EnableSignInWithUsername) 400 props["RequireEmailVerification"] = strconv.FormatBool(c.EmailSettings.RequireEmailVerification) 401 props["EnableEmailBatching"] = strconv.FormatBool(*c.EmailSettings.EnableEmailBatching) 402 props["EmailNotificationContentsType"] = *c.EmailSettings.EmailNotificationContentsType 403 404 props["EmailLoginButtonColor"] = *c.EmailSettings.LoginButtonColor 405 props["EmailLoginButtonBorderColor"] = *c.EmailSettings.LoginButtonBorderColor 406 props["EmailLoginButtonTextColor"] = *c.EmailSettings.LoginButtonTextColor 407 408 props["EnableSignUpWithGitLab"] = strconv.FormatBool(c.GitLabSettings.Enable) 409 410 props["ShowEmailAddress"] = strconv.FormatBool(c.PrivacySettings.ShowEmailAddress) 411 412 props["TermsOfServiceLink"] = *c.SupportSettings.TermsOfServiceLink 413 props["PrivacyPolicyLink"] = *c.SupportSettings.PrivacyPolicyLink 414 props["AboutLink"] = *c.SupportSettings.AboutLink 415 props["HelpLink"] = *c.SupportSettings.HelpLink 416 props["ReportAProblemLink"] = *c.SupportSettings.ReportAProblemLink 417 props["SupportEmail"] = *c.SupportSettings.SupportEmail 418 419 props["EnableFileAttachments"] = strconv.FormatBool(*c.FileSettings.EnableFileAttachments) 420 props["EnableMobileFileUpload"] = strconv.FormatBool(*c.FileSettings.EnableMobileUpload) 421 props["EnableMobileFileDownload"] = strconv.FormatBool(*c.FileSettings.EnableMobileDownload) 422 props["EnablePublicLink"] = strconv.FormatBool(c.FileSettings.EnablePublicLink) 423 424 props["WebsocketPort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketPort) 425 props["WebsocketSecurePort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketSecurePort) 426 427 props["DefaultClientLocale"] = *c.LocalizationSettings.DefaultClientLocale 428 props["AvailableLocales"] = *c.LocalizationSettings.AvailableLocales 429 props["SQLDriverName"] = *c.SqlSettings.DriverName 430 431 props["EnableCustomEmoji"] = strconv.FormatBool(*c.ServiceSettings.EnableCustomEmoji) 432 props["EnableEmojiPicker"] = strconv.FormatBool(*c.ServiceSettings.EnableEmojiPicker) 433 props["RestrictCustomEmojiCreation"] = *c.ServiceSettings.RestrictCustomEmojiCreation 434 props["MaxFileSize"] = strconv.FormatInt(*c.FileSettings.MaxFileSize, 10) 435 props["AppDownloadLink"] = *c.NativeAppSettings.AppDownloadLink 436 props["AndroidAppDownloadLink"] = *c.NativeAppSettings.AndroidAppDownloadLink 437 props["IosAppDownloadLink"] = *c.NativeAppSettings.IosAppDownloadLink 438 439 props["EnableWebrtc"] = strconv.FormatBool(*c.WebrtcSettings.Enable) 440 441 props["MaxNotificationsPerChannel"] = strconv.FormatInt(*c.TeamSettings.MaxNotificationsPerChannel, 10) 442 props["EnableConfirmNotificationsToChannel"] = strconv.FormatBool(*c.TeamSettings.EnableConfirmNotificationsToChannel) 443 props["TimeBetweenUserTypingUpdatesMilliseconds"] = strconv.FormatInt(*c.ServiceSettings.TimeBetweenUserTypingUpdatesMilliseconds, 10) 444 props["EnableUserTypingMessages"] = strconv.FormatBool(*c.ServiceSettings.EnableUserTypingMessages) 445 props["EnableChannelViewedMessages"] = strconv.FormatBool(*c.ServiceSettings.EnableChannelViewedMessages) 446 447 props["DiagnosticId"] = diagnosticId 448 props["DiagnosticsEnabled"] = strconv.FormatBool(*c.LogSettings.EnableDiagnostics) 449 450 props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) 451 452 hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != "" 453 props["HasImageProxy"] = strconv.FormatBool(hasImageProxy) 454 455 if license != nil { 456 props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) 457 props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) 458 459 if *license.Features.CustomBrand { 460 props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand) 461 props["CustomBrandText"] = *c.TeamSettings.CustomBrandText 462 props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText 463 } 464 465 if *license.Features.LDAP { 466 props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable) 467 props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName 468 props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "") 469 props["LdapFirstNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.FirstNameAttribute != "") 470 props["LdapLastNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.LastNameAttribute != "") 471 props["LdapLoginButtonColor"] = *c.LdapSettings.LoginButtonColor 472 props["LdapLoginButtonBorderColor"] = *c.LdapSettings.LoginButtonBorderColor 473 props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor 474 } 475 476 if *license.Features.MFA { 477 props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication) 478 props["EnforceMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnforceMultifactorAuthentication) 479 } 480 481 if *license.Features.Compliance { 482 props["EnableCompliance"] = strconv.FormatBool(*c.ComplianceSettings.Enable) 483 } 484 485 if *license.Features.SAML { 486 props["EnableSaml"] = strconv.FormatBool(*c.SamlSettings.Enable) 487 props["SamlLoginButtonText"] = *c.SamlSettings.LoginButtonText 488 props["SamlFirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "") 489 props["SamlLastNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.LastNameAttribute != "") 490 props["SamlNicknameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.NicknameAttribute != "") 491 props["SamlLoginButtonColor"] = *c.SamlSettings.LoginButtonColor 492 props["SamlLoginButtonBorderColor"] = *c.SamlSettings.LoginButtonBorderColor 493 props["SamlLoginButtonTextColor"] = *c.SamlSettings.LoginButtonTextColor 494 } 495 496 if *license.Features.Cluster { 497 props["EnableCluster"] = strconv.FormatBool(*c.ClusterSettings.Enable) 498 } 499 500 if *license.Features.Cluster { 501 props["EnableMetrics"] = strconv.FormatBool(*c.MetricsSettings.Enable) 502 } 503 504 if *license.Features.GoogleOAuth { 505 props["EnableSignUpWithGoogle"] = strconv.FormatBool(c.GoogleSettings.Enable) 506 } 507 508 if *license.Features.Office365OAuth { 509 props["EnableSignUpWithOffice365"] = strconv.FormatBool(c.Office365Settings.Enable) 510 } 511 512 if *license.Features.PasswordRequirements { 513 props["PasswordMinimumLength"] = fmt.Sprintf("%v", *c.PasswordSettings.MinimumLength) 514 props["PasswordRequireLowercase"] = strconv.FormatBool(*c.PasswordSettings.Lowercase) 515 props["PasswordRequireUppercase"] = strconv.FormatBool(*c.PasswordSettings.Uppercase) 516 props["PasswordRequireNumber"] = strconv.FormatBool(*c.PasswordSettings.Number) 517 props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol) 518 } 519 520 if *license.Features.Announcement { 521 props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner) 522 props["BannerText"] = *c.AnnouncementSettings.BannerText 523 props["BannerColor"] = *c.AnnouncementSettings.BannerColor 524 props["BannerTextColor"] = *c.AnnouncementSettings.BannerTextColor 525 props["AllowBannerDismissal"] = strconv.FormatBool(*c.AnnouncementSettings.AllowBannerDismissal) 526 } 527 528 if *license.Features.ThemeManagement { 529 props["EnableThemeSelection"] = strconv.FormatBool(*c.ThemeSettings.EnableThemeSelection) 530 props["DefaultTheme"] = *c.ThemeSettings.DefaultTheme 531 props["AllowCustomThemes"] = strconv.FormatBool(*c.ThemeSettings.AllowCustomThemes) 532 props["AllowedThemes"] = strings.Join(c.ThemeSettings.AllowedThemes, ",") 533 } 534 535 if *license.Features.DataRetention { 536 props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion) 537 props["DataRetentionMessageRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.MessageRetentionDays), 10) 538 props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion) 539 props["DataRetentionFileRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.FileRetentionDays), 10) 540 } 541 } 542 543 return props 544 } 545 546 func ValidateLdapFilter(cfg *model.Config, ldap einterfaces.LdapInterface) *model.AppError { 547 if *cfg.LdapSettings.Enable && ldap != nil && *cfg.LdapSettings.UserFilter != "" { 548 if err := ldap.ValidateFilter(*cfg.LdapSettings.UserFilter); err != nil { 549 return err 550 } 551 } 552 return nil 553 } 554 555 func ValidateLocales(cfg *model.Config) *model.AppError { 556 var err *model.AppError 557 locales := GetSupportedLocales() 558 if _, ok := locales[*cfg.LocalizationSettings.DefaultServerLocale]; !ok { 559 *cfg.LocalizationSettings.DefaultServerLocale = model.DEFAULT_LOCALE 560 err = model.NewAppError("ValidateLocales", "utils.config.supported_server_locale.app_error", nil, "", http.StatusBadRequest) 561 } 562 563 if _, ok := locales[*cfg.LocalizationSettings.DefaultClientLocale]; !ok { 564 *cfg.LocalizationSettings.DefaultClientLocale = model.DEFAULT_LOCALE 565 err = model.NewAppError("ValidateLocales", "utils.config.supported_client_locale.app_error", nil, "", http.StatusBadRequest) 566 } 567 568 if len(*cfg.LocalizationSettings.AvailableLocales) > 0 { 569 isDefaultClientLocaleInAvailableLocales := false 570 for _, word := range strings.Split(*cfg.LocalizationSettings.AvailableLocales, ",") { 571 if _, ok := locales[word]; !ok { 572 *cfg.LocalizationSettings.AvailableLocales = "" 573 isDefaultClientLocaleInAvailableLocales = true 574 err = model.NewAppError("ValidateLocales", "utils.config.supported_available_locales.app_error", nil, "", http.StatusBadRequest) 575 break 576 } 577 578 if word == *cfg.LocalizationSettings.DefaultClientLocale { 579 isDefaultClientLocaleInAvailableLocales = true 580 } 581 } 582 583 availableLocales := *cfg.LocalizationSettings.AvailableLocales 584 585 if !isDefaultClientLocaleInAvailableLocales { 586 availableLocales += "," + *cfg.LocalizationSettings.DefaultClientLocale 587 err = model.NewAppError("ValidateLocales", "utils.config.add_client_locale.app_error", nil, "", http.StatusBadRequest) 588 } 589 590 *cfg.LocalizationSettings.AvailableLocales = strings.Join(RemoveDuplicatesFromStringArray(strings.Split(availableLocales, ",")), ",") 591 } 592 593 return err 594 }