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  }