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  }