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