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