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  }