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