hunk.code_window: func (a *App) regenerateClientConfig() {
   	a.Srv.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
   	if a.Srv.clientConfig["EnableCustomTermsOfService"] == "true" {
   		termsOfService, err := a.GetLatestTermsOfService()
   		if err != nil {
   			mlog.Err(err)
  
hunk.labels: keep keep add keep keep keep keep keep
hunk.after_edit: 	a.Srv.limitedClientConfig = utils.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License())
  
hunk.file_path: app/config.go
hunk.type: add
file: // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
  // See License.txt for license information.
  
  package app
  
  import (
  	"crypto/ecdsa"
  	"crypto/elliptic"
  	"crypto/md5"
  	"crypto/rand"
  	"crypto/x509"
  	"encoding/base64"
  	"encoding/json"
  	"fmt"
  	"net/url"
  	"runtime/debug"
  	"strconv"
  	"strings"
  	"time"
  
  	"github.com/mattermost/mattermost-server/mlog"
  	"github.com/mattermost/mattermost-server/model"
  	"github.com/mattermost/mattermost-server/utils"
  )
  
  const (
  	ERROR_TERMS_OF_SERVICE_NO_ROWS_FOUND = "store.sql_terms_of_service_store.get.no_rows.app_error"
  )
  
  func (s *Server) Config() *model.Config {
  	if cfg := s.config.Load(); cfg != nil {
  		return cfg.(*model.Config)
  	}
  	return &model.Config{}
  }
  
  func (a *App) Config() *model.Config {
  	return a.Srv.Config()
  }
  
  func (s *Server) EnvironmentConfig() map[string]interface{} {
  	if s.envConfig != nil {
  		return s.envConfig
  	}
  	return map[string]interface{}{}
  }
  
  func (a *App) EnvironmentConfig() map[string]interface{} {
  	return a.Srv.EnvironmentConfig()
  }
  
  func (s *Server) UpdateConfig(f func(*model.Config)) {
  	old := s.Config()
  	updated := old.Clone()
  	f(updated)
  	s.config.Store(updated)
  
  	s.InvokeConfigListeners(old, updated)
  }
  
  func (a *App) UpdateConfig(f func(*model.Config)) {
  	a.Srv.UpdateConfig(f)
  }
  
  func (a *App) PersistConfig() {
  	utils.SaveConfig(a.ConfigFileName(), a.Config())
  }
  
  func (s *Server) LoadConfig(configFile string) *model.AppError {
  	old := s.Config()
  
  	cfg, configPath, envConfig, err := utils.LoadConfig(configFile)
  	if err != nil {
  		return err
  	}
  	*cfg.ServiceSettings.SiteURL = strings.TrimRight(*cfg.ServiceSettings.SiteURL, "/")
  	s.config.Store(cfg)
  
  	s.configFile = configPath
  	s.envConfig = envConfig
  
  	s.InvokeConfigListeners(old, cfg)
  	return nil
  }
  
  func (a *App) LoadConfig(configFile string) *model.AppError {
  	return a.Srv.LoadConfig(configFile)
  }
  
  func (s *Server) ReloadConfig() *model.AppError {
  	debug.FreeOSMemory()
  	if err := s.LoadConfig(s.configFile); err != nil {
  		return err
  	}
  	return nil
  }
  
  func (a *App) ReloadConfig() *model.AppError {
  	return a.Srv.ReloadConfig()
  }
  
  func (a *App) ConfigFileName() string {
  	return a.Srv.configFile
  }
  
  func (a *App) ClientConfig() map[string]string {
  	return a.Srv.clientConfig
  }
  
  func (a *App) ClientConfigHash() string {
  	return a.Srv.clientConfigHash
  }
  
  func (a *App) LimitedClientConfig() map[string]string {
  	return a.Srv.limitedClientConfig
  }
  
  func (s *Server) EnableConfigWatch() {
  	if s.configWatcher == nil && !s.disableConfigWatch {
  		configWatcher, err := utils.NewConfigWatcher(s.configFile, func() {
  			s.ReloadConfig()
  		})
  		if err != nil {
  			mlog.Error(fmt.Sprint(err))
  		}
  		s.configWatcher = configWatcher
  	}
  }
  
  func (a *App) EnableConfigWatch() {
  	a.Srv.EnableConfigWatch()
  }
  
  func (s *Server) DisableConfigWatch() {
  	if s.configWatcher != nil {
  		s.configWatcher.Close()
  		s.configWatcher = nil
  	}
  }
  
  func (a *App) DisableConfigWatch() {
  	a.Srv.DisableConfigWatch()
  }
  
  // Registers a function with a given to be called when the config is reloaded and may have changed. The function
  // will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
  // for the listener that can later be used to remove it.
  func (s *Server) AddConfigListener(listener func(*model.Config, *model.Config)) string {
  	id := model.NewId()
  	s.configListeners[id] = listener
  	return id
  }
  
  func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
  	return a.Srv.AddConfigListener(listener)
  }
  
  // Removes a listener function by the unique ID returned when AddConfigListener was called
  func (s *Server) RemoveConfigListener(id string) {
  	delete(s.configListeners, id)
  }
  
  func (a *App) RemoveConfigListener(id string) {
  	a.Srv.RemoveConfigListener(id)
  }
  
  func (s *Server) InvokeConfigListeners(old, current *model.Config) {
  	for _, listener := range s.configListeners {
  		listener(old, current)
  	}
  }
  
  // EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to
  // AsymmetricSigningKey will always return a valid signing key.
  func (a *App) ensureAsymmetricSigningKey() error {
  	if a.Srv.asymmetricSigningKey != nil {
  		return nil
  	}
  
  	var key *model.SystemAsymmetricSigningKey
  
  	result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
  	if result.Err == nil {
  		if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
  			return err
  		}
  	}
  
  	// If we don't already have a key, try to generate one.
  	if key == nil {
  		newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  		if err != nil {
  			return err
  		}
  		newKey := &model.SystemAsymmetricSigningKey{
  			ECDSAKey: &model.SystemECDSAKey{
  				Curve: "P-256",
  				X:     newECDSAKey.X,
  				Y:     newECDSAKey.Y,
  				D:     newECDSAKey.D,
  			},
  		}
  		system := &model.System{
  			Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY,
  		}
  		v, err := json.Marshal(newKey)
  		if err != nil {
  			return err
  		}
  		system.Value = string(v)
  		if result = <-a.Srv.Store.System().Save(system); result.Err == nil {
  			// If we were able to save the key, use it, otherwise ignore the error.
  			key = newKey
  		}
  	}
  
  	// If we weren't able to save a new key above, another server must have beat us to it. Get the
  	// key from the database, and if that fails, error out.
  	if key == nil {
  		result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
  		if result.Err != nil {
  			return result.Err
  		}
  
  		if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
  			return err
  		}
  	}
  
  	var curve elliptic.Curve
  	switch key.ECDSAKey.Curve {
  	case "P-256":
  		curve = elliptic.P256()
  	default:
  		return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve)
  	}
  	a.Srv.asymmetricSigningKey = &ecdsa.PrivateKey{
  		PublicKey: ecdsa.PublicKey{
  			Curve: curve,
  			X:     key.ECDSAKey.X,
  			Y:     key.ECDSAKey.Y,
  		},
  		D: key.ECDSAKey.D,
  	}
  	a.regenerateClientConfig()
  	return nil
  }
  
  func (a *App) ensureInstallationDate() error {
  	_, err := a.getSystemInstallDate()
  	if err == nil {
  		return nil
  	}
  
  	result := <-a.Srv.Store.User().InferSystemInstallDate()
  	var installationDate int64
  	if result.Err == nil && result.Data.(int64) > 0 {
  		installationDate = result.Data.(int64)
  	} else {
  		installationDate = utils.MillisFromTime(time.Now())
  	}
  
  	result = <-a.Srv.Store.System().SaveOrUpdate(&model.System{
  		Name:  model.SYSTEM_INSTALLATION_DATE_KEY,
  		Value: strconv.FormatInt(installationDate, 10),
  	})
  	if result.Err != nil {
  		return result.Err
  	}
  	return nil
  }
  
  // AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
  func (s *Server) AsymmetricSigningKey() *ecdsa.PrivateKey {
  	return s.asymmetricSigningKey
  }
  
  func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
  	return a.Srv.AsymmetricSigningKey()
  }
  
  func (a *App) regenerateClientConfig() {
  	a.Srv.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
  
  	if a.Srv.clientConfig["EnableCustomTermsOfService"] == "true" {
  		termsOfService, err := a.GetLatestTermsOfService()
  		if err != nil {
  			mlog.Err(err)
  		} else {
  			a.Srv.clientConfig["CustomTermsOfServiceId"] = termsOfService.Id
  		}
  	}
  
  	a.Srv.limitedClientConfig = utils.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License())
  
  	if key := a.AsymmetricSigningKey(); key != nil {
  		der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
  		a.Srv.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
  		a.Srv.limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
  	}
  
  	clientConfigJSON, _ := json.Marshal(a.Srv.clientConfig)
  	a.Srv.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
  }
  
  func (a *App) Desanitize(cfg *model.Config) {
  	actual := a.Config()
  
  	if cfg.LdapSettings.BindPassword != nil && *cfg.LdapSettings.BindPassword == model.FAKE_SETTING {
  		*cfg.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword
  	}
  
  	if *cfg.FileSettings.PublicLinkSalt == model.FAKE_SETTING {
  		*cfg.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt
  	}
  	if cfg.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING {
  		cfg.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey
  	}
  
  	if cfg.EmailSettings.InviteSalt == model.FAKE_SETTING {
  		cfg.EmailSettings.InviteSalt = actual.EmailSettings.InviteSalt
  	}
  	if cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
  		cfg.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword
  	}
  
  	if cfg.GitLabSettings.Secret == model.FAKE_SETTING {
  		cfg.GitLabSettings.Secret = actual.GitLabSettings.Secret
  	}
  
  	if *cfg.SqlSettings.DataSource == model.FAKE_SETTING {
  		*cfg.SqlSettings.DataSource = *actual.SqlSettings.DataSource
  	}
  	if cfg.SqlSettings.AtRestEncryptKey == model.FAKE_SETTING {
  		cfg.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey
  	}
  
  	if *cfg.ElasticsearchSettings.Password == model.FAKE_SETTING {
  		*cfg.ElasticsearchSettings.Password = *actual.ElasticsearchSettings.Password
  	}
  
  	for i := range cfg.SqlSettings.DataSourceReplicas {
  		cfg.SqlSettings.DataSourceReplicas[i] = actual.SqlSettings.DataSourceReplicas[i]
  	}
  
  	for i := range cfg.SqlSettings.DataSourceSearchReplicas {
  		cfg.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i]
  	}
  }
  
  func (a *App) GetCookieDomain() string {
  	if *a.Config().ServiceSettings.AllowCookiesForSubdomains {
  		if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil {
  			return siteURL.Hostname()
  		}
  	}
  	return ""
  }
  
  func (a *App) GetSiteURL() string {
  	return *a.Config().ServiceSettings.SiteURL
  }
  
  // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
  func (a *App) ClientConfigWithComputed() map[string]string {
  	respCfg := map[string]string{}
  	for k, v := range a.ClientConfig() {
  		respCfg[k] = v
  	}
  
  	// These properties are not configurable, but nevertheless represent configuration expected
  	// by the client.
  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
  	respCfg["MaxPostSize"] = strconv.Itoa(a.MaxPostSize())
  	respCfg["InstallationDate"] = ""
  	if installationDate, err := a.getSystemInstallDate(); err == nil {
  		respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10)
  	}
  
  	return respCfg
  }
  
  // LimitedClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
  func (a *App) LimitedClientConfigWithComputed() map[string]string {
  	respCfg := map[string]string{}
  	for k, v := range a.LimitedClientConfig() {
  		respCfg[k] = v
  	}
  
  	// These properties are not configurable, but nevertheless represent configuration expected
  	// by the client.
  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
  
  	return respCfg
  }
  