github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+incompatible/app/config.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"crypto/ecdsa"
     8  	"crypto/elliptic"
     9  	"crypto/md5"
    10  	"crypto/rand"
    11  	"crypto/x509"
    12  	"encoding/base64"
    13  	"encoding/json"
    14  	"fmt"
    15  	"net/url"
    16  	"runtime/debug"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/mattermost/mattermost-server/mlog"
    22  	"github.com/mattermost/mattermost-server/model"
    23  	"github.com/mattermost/mattermost-server/utils"
    24  )
    25  
    26  const (
    27  	ERROR_SERVICE_TERMS_NO_ROWS_FOUND = "store.sql_service_terms_store.get.no_rows.app_error"
    28  )
    29  
    30  func (a *App) Config() *model.Config {
    31  	if cfg := a.config.Load(); cfg != nil {
    32  		return cfg.(*model.Config)
    33  	}
    34  	return &model.Config{}
    35  }
    36  
    37  func (a *App) EnvironmentConfig() map[string]interface{} {
    38  	if a.envConfig != nil {
    39  		return a.envConfig
    40  	}
    41  	return map[string]interface{}{}
    42  }
    43  
    44  func (a *App) UpdateConfig(f func(*model.Config)) {
    45  	old := a.Config()
    46  	updated := old.Clone()
    47  	f(updated)
    48  	a.config.Store(updated)
    49  
    50  	a.InvokeConfigListeners(old, updated)
    51  }
    52  
    53  func (a *App) PersistConfig() {
    54  	utils.SaveConfig(a.ConfigFileName(), a.Config())
    55  }
    56  
    57  func (a *App) LoadConfig(configFile string) *model.AppError {
    58  	old := a.Config()
    59  
    60  	cfg, configPath, envConfig, err := utils.LoadConfig(configFile)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	a.configFile = configPath
    66  
    67  	a.config.Store(cfg)
    68  	a.envConfig = envConfig
    69  
    70  	a.siteURL = strings.TrimRight(*cfg.ServiceSettings.SiteURL, "/")
    71  
    72  	a.InvokeConfigListeners(old, cfg)
    73  	return nil
    74  }
    75  
    76  func (a *App) ReloadConfig() *model.AppError {
    77  	debug.FreeOSMemory()
    78  	if err := a.LoadConfig(a.configFile); err != nil {
    79  		return err
    80  	}
    81  
    82  	// start/restart email batching job if necessary
    83  	a.InitEmailBatching()
    84  	return nil
    85  }
    86  
    87  func (a *App) ConfigFileName() string {
    88  	return a.configFile
    89  }
    90  
    91  func (a *App) ClientConfig() map[string]string {
    92  	return a.clientConfig
    93  }
    94  
    95  func (a *App) ClientConfigHash() string {
    96  	return a.clientConfigHash
    97  }
    98  
    99  func (a *App) LimitedClientConfig() map[string]string {
   100  	return a.limitedClientConfig
   101  }
   102  
   103  func (a *App) EnableConfigWatch() {
   104  	if a.configWatcher == nil && !a.disableConfigWatch {
   105  		configWatcher, err := utils.NewConfigWatcher(a.ConfigFileName(), func() {
   106  			a.ReloadConfig()
   107  		})
   108  		if err != nil {
   109  			mlog.Error(fmt.Sprint(err))
   110  		}
   111  		a.configWatcher = configWatcher
   112  	}
   113  }
   114  
   115  func (a *App) DisableConfigWatch() {
   116  	if a.configWatcher != nil {
   117  		a.configWatcher.Close()
   118  		a.configWatcher = nil
   119  	}
   120  }
   121  
   122  // Registers a function with a given to be called when the config is reloaded and may have changed. The function
   123  // will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
   124  // for the listener that can later be used to remove it.
   125  func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
   126  	id := model.NewId()
   127  	a.configListeners[id] = listener
   128  	return id
   129  }
   130  
   131  // Removes a listener function by the unique ID returned when AddConfigListener was called
   132  func (a *App) RemoveConfigListener(id string) {
   133  	delete(a.configListeners, id)
   134  }
   135  
   136  func (a *App) InvokeConfigListeners(old, current *model.Config) {
   137  	for _, listener := range a.configListeners {
   138  		listener(old, current)
   139  	}
   140  }
   141  
   142  // EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to
   143  // AsymmetricSigningKey will always return a valid signing key.
   144  func (a *App) ensureAsymmetricSigningKey() error {
   145  	if a.asymmetricSigningKey != nil {
   146  		return nil
   147  	}
   148  
   149  	var key *model.SystemAsymmetricSigningKey
   150  
   151  	result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   152  	if result.Err == nil {
   153  		if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
   154  			return err
   155  		}
   156  	}
   157  
   158  	// If we don't already have a key, try to generate one.
   159  	if key == nil {
   160  		newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   161  		if err != nil {
   162  			return err
   163  		}
   164  		newKey := &model.SystemAsymmetricSigningKey{
   165  			ECDSAKey: &model.SystemECDSAKey{
   166  				Curve: "P-256",
   167  				X:     newECDSAKey.X,
   168  				Y:     newECDSAKey.Y,
   169  				D:     newECDSAKey.D,
   170  			},
   171  		}
   172  		system := &model.System{
   173  			Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY,
   174  		}
   175  		v, err := json.Marshal(newKey)
   176  		if err != nil {
   177  			return err
   178  		}
   179  		system.Value = string(v)
   180  		if result = <-a.Srv.Store.System().Save(system); result.Err == nil {
   181  			// If we were able to save the key, use it, otherwise ignore the error.
   182  			key = newKey
   183  		}
   184  	}
   185  
   186  	// If we weren't able to save a new key above, another server must have beat us to it. Get the
   187  	// key from the database, and if that fails, error out.
   188  	if key == nil {
   189  		result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   190  		if result.Err != nil {
   191  			return result.Err
   192  		}
   193  
   194  		if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	var curve elliptic.Curve
   200  	switch key.ECDSAKey.Curve {
   201  	case "P-256":
   202  		curve = elliptic.P256()
   203  	default:
   204  		return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve)
   205  	}
   206  	a.asymmetricSigningKey = &ecdsa.PrivateKey{
   207  		PublicKey: ecdsa.PublicKey{
   208  			Curve: curve,
   209  			X:     key.ECDSAKey.X,
   210  			Y:     key.ECDSAKey.Y,
   211  		},
   212  		D: key.ECDSAKey.D,
   213  	}
   214  	a.regenerateClientConfig()
   215  	return nil
   216  }
   217  
   218  func (a *App) ensureInstallationDate() error {
   219  	_, err := a.getSystemInstallDate()
   220  	if err == nil {
   221  		return nil
   222  	}
   223  
   224  	result := <-a.Srv.Store.User().InferSystemInstallDate()
   225  	var installationDate int64
   226  	if result.Err == nil && result.Data.(int64) > 0 {
   227  		installationDate = result.Data.(int64)
   228  	} else {
   229  		installationDate = utils.MillisFromTime(time.Now())
   230  	}
   231  
   232  	result = <-a.Srv.Store.System().SaveOrUpdate(&model.System{
   233  		Name:  model.SYSTEM_INSTALLATION_DATE_KEY,
   234  		Value: strconv.FormatInt(installationDate, 10),
   235  	})
   236  	if result.Err != nil {
   237  		return result.Err
   238  	}
   239  	return nil
   240  }
   241  
   242  // AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
   243  func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
   244  	return a.asymmetricSigningKey
   245  }
   246  
   247  func (a *App) regenerateClientConfig() {
   248  	a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
   249  
   250  	if a.clientConfig["EnableCustomServiceTerms"] == "true" {
   251  		serviceTerms, err := a.GetLatestServiceTerms()
   252  		if err != nil {
   253  			mlog.Err(err)
   254  		} else {
   255  			a.clientConfig["CustomServiceTermsId"] = serviceTerms.Id
   256  		}
   257  	}
   258  
   259  	a.limitedClientConfig = utils.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License())
   260  
   261  	if key := a.AsymmetricSigningKey(); key != nil {
   262  		der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
   263  		a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
   264  		a.limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
   265  	}
   266  
   267  	clientConfigJSON, _ := json.Marshal(a.clientConfig)
   268  	a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
   269  }
   270  
   271  func (a *App) Desanitize(cfg *model.Config) {
   272  	actual := a.Config()
   273  
   274  	if cfg.LdapSettings.BindPassword != nil && *cfg.LdapSettings.BindPassword == model.FAKE_SETTING {
   275  		*cfg.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword
   276  	}
   277  
   278  	if *cfg.FileSettings.PublicLinkSalt == model.FAKE_SETTING {
   279  		*cfg.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt
   280  	}
   281  	if cfg.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING {
   282  		cfg.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey
   283  	}
   284  
   285  	if cfg.EmailSettings.InviteSalt == model.FAKE_SETTING {
   286  		cfg.EmailSettings.InviteSalt = actual.EmailSettings.InviteSalt
   287  	}
   288  	if cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
   289  		cfg.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword
   290  	}
   291  
   292  	if cfg.GitLabSettings.Secret == model.FAKE_SETTING {
   293  		cfg.GitLabSettings.Secret = actual.GitLabSettings.Secret
   294  	}
   295  
   296  	if *cfg.SqlSettings.DataSource == model.FAKE_SETTING {
   297  		*cfg.SqlSettings.DataSource = *actual.SqlSettings.DataSource
   298  	}
   299  	if cfg.SqlSettings.AtRestEncryptKey == model.FAKE_SETTING {
   300  		cfg.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey
   301  	}
   302  
   303  	if *cfg.ElasticsearchSettings.Password == model.FAKE_SETTING {
   304  		*cfg.ElasticsearchSettings.Password = *actual.ElasticsearchSettings.Password
   305  	}
   306  
   307  	for i := range cfg.SqlSettings.DataSourceReplicas {
   308  		cfg.SqlSettings.DataSourceReplicas[i] = actual.SqlSettings.DataSourceReplicas[i]
   309  	}
   310  
   311  	for i := range cfg.SqlSettings.DataSourceSearchReplicas {
   312  		cfg.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i]
   313  	}
   314  }
   315  
   316  func (a *App) GetCookieDomain() string {
   317  	if *a.Config().ServiceSettings.AllowCookiesForSubdomains {
   318  		if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil {
   319  			return siteURL.Hostname()
   320  		}
   321  	}
   322  	return ""
   323  }
   324  
   325  func (a *App) GetSiteURL() string {
   326  	return a.siteURL
   327  }
   328  
   329  // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   330  func (a *App) ClientConfigWithComputed() map[string]string {
   331  	respCfg := map[string]string{}
   332  	for k, v := range a.ClientConfig() {
   333  		respCfg[k] = v
   334  	}
   335  
   336  	// These properties are not configurable, but nevertheless represent configuration expected
   337  	// by the client.
   338  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
   339  	respCfg["MaxPostSize"] = strconv.Itoa(a.MaxPostSize())
   340  	respCfg["InstallationDate"] = ""
   341  	if installationDate, err := a.getSystemInstallDate(); err == nil {
   342  		respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10)
   343  	}
   344  
   345  	return respCfg
   346  }
   347  
   348  // LimitedClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   349  func (a *App) LimitedClientConfigWithComputed() map[string]string {
   350  	respCfg := map[string]string{}
   351  	for k, v := range a.LimitedClientConfig() {
   352  		respCfg[k] = v
   353  	}
   354  
   355  	// These properties are not configurable, but nevertheless represent configuration expected
   356  	// by the client.
   357  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
   358  
   359  	return respCfg
   360  }