github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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  
    20  	"github.com/mattermost/mattermost-server/mlog"
    21  	"github.com/mattermost/mattermost-server/model"
    22  	"github.com/mattermost/mattermost-server/utils"
    23  )
    24  
    25  func (a *App) Config() *model.Config {
    26  	if cfg := a.config.Load(); cfg != nil {
    27  		return cfg.(*model.Config)
    28  	}
    29  	return &model.Config{}
    30  }
    31  
    32  func (a *App) EnvironmentConfig() map[string]interface{} {
    33  	if a.envConfig != nil {
    34  		return a.envConfig
    35  	}
    36  	return map[string]interface{}{}
    37  }
    38  
    39  func (a *App) UpdateConfig(f func(*model.Config)) {
    40  	old := a.Config()
    41  	updated := old.Clone()
    42  	f(updated)
    43  	a.config.Store(updated)
    44  
    45  	a.InvokeConfigListeners(old, updated)
    46  }
    47  
    48  func (a *App) PersistConfig() {
    49  	utils.SaveConfig(a.ConfigFileName(), a.Config())
    50  }
    51  
    52  func (a *App) LoadConfig(configFile string) *model.AppError {
    53  	old := a.Config()
    54  
    55  	cfg, configPath, envConfig, err := utils.LoadConfig(configFile)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	a.configFile = configPath
    61  
    62  	a.config.Store(cfg)
    63  	a.envConfig = envConfig
    64  
    65  	a.siteURL = strings.TrimRight(*cfg.ServiceSettings.SiteURL, "/")
    66  
    67  	a.InvokeConfigListeners(old, cfg)
    68  	return nil
    69  }
    70  
    71  func (a *App) ReloadConfig() *model.AppError {
    72  	debug.FreeOSMemory()
    73  	if err := a.LoadConfig(a.configFile); err != nil {
    74  		return err
    75  	}
    76  
    77  	// start/restart email batching job if necessary
    78  	a.InitEmailBatching()
    79  	return nil
    80  }
    81  
    82  func (a *App) ConfigFileName() string {
    83  	return a.configFile
    84  }
    85  
    86  func (a *App) ClientConfig() map[string]string {
    87  	return a.clientConfig
    88  }
    89  
    90  func (a *App) ClientConfigHash() string {
    91  	return a.clientConfigHash
    92  }
    93  
    94  func (a *App) EnableConfigWatch() {
    95  	if a.configWatcher == nil && !a.disableConfigWatch {
    96  		configWatcher, err := utils.NewConfigWatcher(a.ConfigFileName(), func() {
    97  			a.ReloadConfig()
    98  		})
    99  		if err != nil {
   100  			mlog.Error(fmt.Sprint(err))
   101  		}
   102  		a.configWatcher = configWatcher
   103  	}
   104  }
   105  
   106  func (a *App) DisableConfigWatch() {
   107  	if a.configWatcher != nil {
   108  		a.configWatcher.Close()
   109  		a.configWatcher = nil
   110  	}
   111  }
   112  
   113  // Registers a function with a given to be called when the config is reloaded and may have changed. The function
   114  // will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
   115  // for the listener that can later be used to remove it.
   116  func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
   117  	id := model.NewId()
   118  	a.configListeners[id] = listener
   119  	return id
   120  }
   121  
   122  // Removes a listener function by the unique ID returned when AddConfigListener was called
   123  func (a *App) RemoveConfigListener(id string) {
   124  	delete(a.configListeners, id)
   125  }
   126  
   127  func (a *App) InvokeConfigListeners(old, current *model.Config) {
   128  	for _, listener := range a.configListeners {
   129  		listener(old, current)
   130  	}
   131  }
   132  
   133  // EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to
   134  // AsymmetricSigningKey will always return a valid signing key.
   135  func (a *App) ensureAsymmetricSigningKey() error {
   136  	if a.asymmetricSigningKey != nil {
   137  		return nil
   138  	}
   139  
   140  	var key *model.SystemAsymmetricSigningKey
   141  
   142  	result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   143  	if result.Err == nil {
   144  		if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	// If we don't already have a key, try to generate one.
   150  	if key == nil {
   151  		newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   152  		if err != nil {
   153  			return err
   154  		}
   155  		newKey := &model.SystemAsymmetricSigningKey{
   156  			ECDSAKey: &model.SystemECDSAKey{
   157  				Curve: "P-256",
   158  				X:     newECDSAKey.X,
   159  				Y:     newECDSAKey.Y,
   160  				D:     newECDSAKey.D,
   161  			},
   162  		}
   163  		system := &model.System{
   164  			Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY,
   165  		}
   166  		v, err := json.Marshal(newKey)
   167  		if err != nil {
   168  			return err
   169  		}
   170  		system.Value = string(v)
   171  		if result = <-a.Srv.Store.System().Save(system); result.Err == nil {
   172  			// If we were able to save the key, use it, otherwise ignore the error.
   173  			key = newKey
   174  		}
   175  	}
   176  
   177  	// If we weren't able to save a new key above, another server must have beat us to it. Get the
   178  	// key from the database, and if that fails, error out.
   179  	if key == nil {
   180  		result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   181  		if result.Err != nil {
   182  			return result.Err
   183  		} else if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	var curve elliptic.Curve
   189  	switch key.ECDSAKey.Curve {
   190  	case "P-256":
   191  		curve = elliptic.P256()
   192  	default:
   193  		return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve)
   194  	}
   195  	a.asymmetricSigningKey = &ecdsa.PrivateKey{
   196  		PublicKey: ecdsa.PublicKey{
   197  			Curve: curve,
   198  			X:     key.ECDSAKey.X,
   199  			Y:     key.ECDSAKey.Y,
   200  		},
   201  		D: key.ECDSAKey.D,
   202  	}
   203  	a.regenerateClientConfig()
   204  	return nil
   205  }
   206  
   207  // AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
   208  func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
   209  	return a.asymmetricSigningKey
   210  }
   211  
   212  func (a *App) regenerateClientConfig() {
   213  	a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
   214  	if key := a.AsymmetricSigningKey(); key != nil {
   215  		der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
   216  		a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
   217  	}
   218  	clientConfigJSON, _ := json.Marshal(a.clientConfig)
   219  	a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
   220  }
   221  
   222  func (a *App) Desanitize(cfg *model.Config) {
   223  	actual := a.Config()
   224  
   225  	if cfg.LdapSettings.BindPassword != nil && *cfg.LdapSettings.BindPassword == model.FAKE_SETTING {
   226  		*cfg.LdapSettings.BindPassword = *actual.LdapSettings.BindPassword
   227  	}
   228  
   229  	if *cfg.FileSettings.PublicLinkSalt == model.FAKE_SETTING {
   230  		*cfg.FileSettings.PublicLinkSalt = *actual.FileSettings.PublicLinkSalt
   231  	}
   232  	if cfg.FileSettings.AmazonS3SecretAccessKey == model.FAKE_SETTING {
   233  		cfg.FileSettings.AmazonS3SecretAccessKey = actual.FileSettings.AmazonS3SecretAccessKey
   234  	}
   235  
   236  	if cfg.EmailSettings.InviteSalt == model.FAKE_SETTING {
   237  		cfg.EmailSettings.InviteSalt = actual.EmailSettings.InviteSalt
   238  	}
   239  	if cfg.EmailSettings.SMTPPassword == model.FAKE_SETTING {
   240  		cfg.EmailSettings.SMTPPassword = actual.EmailSettings.SMTPPassword
   241  	}
   242  
   243  	if cfg.GitLabSettings.Secret == model.FAKE_SETTING {
   244  		cfg.GitLabSettings.Secret = actual.GitLabSettings.Secret
   245  	}
   246  
   247  	if cfg.IyoSettings.Secret == model.FAKE_SETTING {
   248  		cfg.IyoSettings.Secret = actual.IyoSettings.Secret
   249  	}
   250  
   251  	if *cfg.SqlSettings.DataSource == model.FAKE_SETTING {
   252  		*cfg.SqlSettings.DataSource = *actual.SqlSettings.DataSource
   253  	}
   254  	if cfg.SqlSettings.AtRestEncryptKey == model.FAKE_SETTING {
   255  		cfg.SqlSettings.AtRestEncryptKey = actual.SqlSettings.AtRestEncryptKey
   256  	}
   257  
   258  	if *cfg.ElasticsearchSettings.Password == model.FAKE_SETTING {
   259  		*cfg.ElasticsearchSettings.Password = *actual.ElasticsearchSettings.Password
   260  	}
   261  
   262  	for i := range cfg.SqlSettings.DataSourceReplicas {
   263  		cfg.SqlSettings.DataSourceReplicas[i] = actual.SqlSettings.DataSourceReplicas[i]
   264  	}
   265  
   266  	for i := range cfg.SqlSettings.DataSourceSearchReplicas {
   267  		cfg.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i]
   268  	}
   269  }
   270  
   271  func (a *App) GetCookieDomain() string {
   272  	if *a.Config().ServiceSettings.AllowCookiesForSubdomains {
   273  		if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil {
   274  			return siteURL.Hostname()
   275  		}
   276  	}
   277  	return ""
   278  }
   279  
   280  func (a *App) GetSiteURL() string {
   281  	return a.siteURL
   282  }
   283  
   284  // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   285  func (a *App) ClientConfigWithComputed() map[string]string {
   286  	respCfg := map[string]string{}
   287  	for k, v := range a.ClientConfig() {
   288  		respCfg[k] = v
   289  	}
   290  
   291  	// These properties are not configurable, but nevertheless represent configuration expected
   292  	// by the client.
   293  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
   294  	respCfg["MaxPostSize"] = strconv.Itoa(a.MaxPostSize())
   295  
   296  	return respCfg
   297  }