github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/app/config.go (about)

     1  // Copyright (c) 2015-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/http"
    16  	"net/url"
    17  	"strconv"
    18  	"time"
    19  
    20  	"github.com/pkg/errors"
    21  
    22  	"github.com/mattermost/mattermost-server/v5/config"
    23  	"github.com/mattermost/mattermost-server/v5/mlog"
    24  	"github.com/mattermost/mattermost-server/v5/model"
    25  	"github.com/mattermost/mattermost-server/v5/services/mailservice"
    26  	"github.com/mattermost/mattermost-server/v5/utils"
    27  )
    28  
    29  const (
    30  	ErrorTermsOfServiceNoRowsFound = "app.terms_of_service.get.no_rows.app_error"
    31  )
    32  
    33  func (s *Server) Config() *model.Config {
    34  	return s.configStore.Get()
    35  }
    36  
    37  func (a *App) Config() *model.Config {
    38  	return a.Srv().Config()
    39  }
    40  
    41  func (s *Server) EnvironmentConfig() map[string]interface{} {
    42  	return s.configStore.GetEnvironmentOverrides()
    43  }
    44  
    45  func (a *App) EnvironmentConfig() map[string]interface{} {
    46  	return a.Srv().EnvironmentConfig()
    47  }
    48  
    49  func (s *Server) UpdateConfig(f func(*model.Config)) {
    50  	if s.configStore.IsReadOnly() {
    51  		return
    52  	}
    53  	old := s.Config()
    54  	updated := old.Clone()
    55  	f(updated)
    56  	if _, err := s.configStore.Set(updated); err != nil {
    57  		mlog.Error("Failed to update config", mlog.Err(err))
    58  	}
    59  }
    60  
    61  func (a *App) UpdateConfig(f func(*model.Config)) {
    62  	a.Srv().UpdateConfig(f)
    63  }
    64  
    65  func (s *Server) ReloadConfig() error {
    66  	if err := s.configStore.Load(); err != nil {
    67  		return err
    68  	}
    69  	return nil
    70  }
    71  
    72  func (a *App) ReloadConfig() error {
    73  	return a.Srv().ReloadConfig()
    74  }
    75  
    76  func (a *App) ClientConfig() map[string]string {
    77  	return a.Srv().clientConfig.Load().(map[string]string)
    78  }
    79  
    80  func (a *App) ClientConfigHash() string {
    81  	return a.Srv().ClientConfigHash()
    82  }
    83  
    84  func (a *App) LimitedClientConfig() map[string]string {
    85  	return a.Srv().limitedClientConfig.Load().(map[string]string)
    86  }
    87  
    88  // Registers a function with a given listener to be called when the config is reloaded and may have changed. The function
    89  // will be called with two arguments: the old config and the new config. AddConfigListener returns a unique ID
    90  // for the listener that can later be used to remove it.
    91  func (s *Server) AddConfigListener(listener func(*model.Config, *model.Config)) string {
    92  	return s.configStore.AddListener(listener)
    93  }
    94  
    95  func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
    96  	return a.Srv().AddConfigListener(listener)
    97  }
    98  
    99  // Removes a listener function by the unique ID returned when AddConfigListener was called
   100  func (s *Server) RemoveConfigListener(id string) {
   101  	s.configStore.RemoveListener(id)
   102  }
   103  
   104  func (a *App) RemoveConfigListener(id string) {
   105  	a.Srv().RemoveConfigListener(id)
   106  }
   107  
   108  // ensurePostActionCookieSecret ensures that the key for encrypting PostActionCookie exists
   109  // and future calls to PostActionCookieSecret will always return a valid key, same on all
   110  // servers in the cluster
   111  func (s *Server) ensurePostActionCookieSecret() error {
   112  	if s.postActionCookieSecret != nil {
   113  		return nil
   114  	}
   115  
   116  	var secret *model.SystemPostActionCookieSecret
   117  
   118  	value, err := s.Store.System().GetByName(model.SYSTEM_POST_ACTION_COOKIE_SECRET)
   119  	if err == nil {
   120  		if err := json.Unmarshal([]byte(value.Value), &secret); err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	// If we don't already have a key, try to generate one.
   126  	if secret == nil {
   127  		newSecret := &model.SystemPostActionCookieSecret{
   128  			Secret: make([]byte, 32),
   129  		}
   130  		_, err := rand.Reader.Read(newSecret.Secret)
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		system := &model.System{
   136  			Name: model.SYSTEM_POST_ACTION_COOKIE_SECRET,
   137  		}
   138  		v, err := json.Marshal(newSecret)
   139  		if err != nil {
   140  			return err
   141  		}
   142  		system.Value = string(v)
   143  		// If we were able to save the key, use it, otherwise log the error.
   144  		if err = s.Store.System().Save(system); err != nil {
   145  			mlog.Warn("Failed to save PostActionCookieSecret", mlog.Err(err))
   146  		} else {
   147  			secret = newSecret
   148  		}
   149  	}
   150  
   151  	// If we weren't able to save a new key above, another server must have beat us to it. Get the
   152  	// key from the database, and if that fails, error out.
   153  	if secret == nil {
   154  		value, err := s.Store.System().GetByName(model.SYSTEM_POST_ACTION_COOKIE_SECRET)
   155  		if err != nil {
   156  			return err
   157  		}
   158  
   159  		if err := json.Unmarshal([]byte(value.Value), &secret); err != nil {
   160  			return err
   161  		}
   162  	}
   163  
   164  	s.postActionCookieSecret = secret.Secret
   165  	return nil
   166  }
   167  
   168  // ensureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to
   169  // AsymmetricSigningKey will always return a valid signing key.
   170  func (s *Server) ensureAsymmetricSigningKey() error {
   171  	if s.AsymmetricSigningKey() != nil {
   172  		return nil
   173  	}
   174  
   175  	var key *model.SystemAsymmetricSigningKey
   176  
   177  	value, err := s.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   178  	if err == nil {
   179  		if err := json.Unmarshal([]byte(value.Value), &key); err != nil {
   180  			return err
   181  		}
   182  	}
   183  
   184  	// If we don't already have a key, try to generate one.
   185  	if key == nil {
   186  		newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
   187  		if err != nil {
   188  			return err
   189  		}
   190  		newKey := &model.SystemAsymmetricSigningKey{
   191  			ECDSAKey: &model.SystemECDSAKey{
   192  				Curve: "P-256",
   193  				X:     newECDSAKey.X,
   194  				Y:     newECDSAKey.Y,
   195  				D:     newECDSAKey.D,
   196  			},
   197  		}
   198  		system := &model.System{
   199  			Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY,
   200  		}
   201  		v, err := json.Marshal(newKey)
   202  		if err != nil {
   203  			return err
   204  		}
   205  		system.Value = string(v)
   206  		// If we were able to save the key, use it, otherwise log the error.
   207  		if err = s.Store.System().Save(system); err != nil {
   208  			mlog.Warn("Failed to save AsymmetricSigningKey", mlog.Err(err))
   209  		} else {
   210  			key = newKey
   211  		}
   212  	}
   213  
   214  	// If we weren't able to save a new key above, another server must have beat us to it. Get the
   215  	// key from the database, and if that fails, error out.
   216  	if key == nil {
   217  		value, err := s.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY)
   218  		if err != nil {
   219  			return err
   220  		}
   221  
   222  		if err := json.Unmarshal([]byte(value.Value), &key); err != nil {
   223  			return err
   224  		}
   225  	}
   226  
   227  	var curve elliptic.Curve
   228  	switch key.ECDSAKey.Curve {
   229  	case "P-256":
   230  		curve = elliptic.P256()
   231  	default:
   232  		return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve)
   233  	}
   234  	s.asymmetricSigningKey.Store(&ecdsa.PrivateKey{
   235  		PublicKey: ecdsa.PublicKey{
   236  			Curve: curve,
   237  			X:     key.ECDSAKey.X,
   238  			Y:     key.ECDSAKey.Y,
   239  		},
   240  		D: key.ECDSAKey.D,
   241  	})
   242  	s.regenerateClientConfig()
   243  	return nil
   244  }
   245  
   246  func (s *Server) ensureInstallationDate() error {
   247  	_, appErr := s.getSystemInstallDate()
   248  	if appErr == nil {
   249  		return nil
   250  	}
   251  
   252  	installDate, nErr := s.Store.User().InferSystemInstallDate()
   253  	var installationDate int64
   254  	if nErr == nil && installDate > 0 {
   255  		installationDate = installDate
   256  	} else {
   257  		installationDate = utils.MillisFromTime(time.Now())
   258  	}
   259  
   260  	if err := s.Store.System().SaveOrUpdate(&model.System{
   261  		Name:  model.SYSTEM_INSTALLATION_DATE_KEY,
   262  		Value: strconv.FormatInt(installationDate, 10),
   263  	}); err != nil {
   264  		return err
   265  	}
   266  	return nil
   267  }
   268  
   269  func (s *Server) ensureFirstServerRunTimestamp() error {
   270  	_, appErr := s.getFirstServerRunTimestamp()
   271  	if appErr == nil {
   272  		return nil
   273  	}
   274  
   275  	if err := s.Store.System().SaveOrUpdate(&model.System{
   276  		Name:  model.SYSTEM_FIRST_SERVER_RUN_TIMESTAMP_KEY,
   277  		Value: strconv.FormatInt(utils.MillisFromTime(time.Now()), 10),
   278  	}); err != nil {
   279  		return err
   280  	}
   281  	return nil
   282  }
   283  
   284  // AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
   285  func (s *Server) AsymmetricSigningKey() *ecdsa.PrivateKey {
   286  	if key := s.asymmetricSigningKey.Load(); key != nil {
   287  		return key.(*ecdsa.PrivateKey)
   288  	}
   289  	return nil
   290  }
   291  
   292  func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
   293  	return a.Srv().AsymmetricSigningKey()
   294  }
   295  
   296  func (s *Server) PostActionCookieSecret() []byte {
   297  	return s.postActionCookieSecret
   298  }
   299  
   300  func (a *App) PostActionCookieSecret() []byte {
   301  	return a.Srv().PostActionCookieSecret()
   302  }
   303  
   304  func (s *Server) regenerateClientConfig() {
   305  	clientConfig := config.GenerateClientConfig(s.Config(), s.TelemetryId(), s.License())
   306  	limitedClientConfig := config.GenerateLimitedClientConfig(s.Config(), s.TelemetryId(), s.License())
   307  
   308  	if clientConfig["EnableCustomTermsOfService"] == "true" {
   309  		termsOfService, err := s.Store.TermsOfService().GetLatest(true)
   310  		if err != nil {
   311  			mlog.Err(err)
   312  		} else {
   313  			clientConfig["CustomTermsOfServiceId"] = termsOfService.Id
   314  			limitedClientConfig["CustomTermsOfServiceId"] = termsOfService.Id
   315  		}
   316  	}
   317  
   318  	if key := s.AsymmetricSigningKey(); key != nil {
   319  		der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
   320  		clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
   321  		limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
   322  	}
   323  
   324  	clientConfigJSON, _ := json.Marshal(clientConfig)
   325  	s.clientConfig.Store(clientConfig)
   326  	s.limitedClientConfig.Store(limitedClientConfig)
   327  	s.clientConfigHash.Store(fmt.Sprintf("%x", md5.Sum(clientConfigJSON)))
   328  }
   329  
   330  func (a *App) GetCookieDomain() string {
   331  	if *a.Config().ServiceSettings.AllowCookiesForSubdomains {
   332  		if siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL); err == nil {
   333  			return siteURL.Hostname()
   334  		}
   335  	}
   336  	return ""
   337  }
   338  
   339  func (a *App) GetSiteURL() string {
   340  	return *a.Config().ServiceSettings.SiteURL
   341  }
   342  
   343  // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   344  func (s *Server) ClientConfigWithComputed() map[string]string {
   345  	respCfg := map[string]string{}
   346  	for k, v := range s.clientConfig.Load().(map[string]string) {
   347  		respCfg[k] = v
   348  	}
   349  
   350  	// These properties are not configurable, but nevertheless represent configuration expected
   351  	// by the client.
   352  	respCfg["NoAccounts"] = strconv.FormatBool(s.IsFirstUserAccount())
   353  	respCfg["MaxPostSize"] = strconv.Itoa(s.MaxPostSize())
   354  	respCfg["UpgradedFromTE"] = strconv.FormatBool(s.isUpgradedFromTE())
   355  	respCfg["InstallationDate"] = ""
   356  	if installationDate, err := s.getSystemInstallDate(); err == nil {
   357  		respCfg["InstallationDate"] = strconv.FormatInt(installationDate, 10)
   358  	}
   359  
   360  	return respCfg
   361  }
   362  
   363  // ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   364  func (a *App) ClientConfigWithComputed() map[string]string {
   365  	return a.Srv().ClientConfigWithComputed()
   366  }
   367  
   368  // LimitedClientConfigWithComputed gets the configuration in a format suitable for sending to the client.
   369  func (a *App) LimitedClientConfigWithComputed() map[string]string {
   370  	respCfg := map[string]string{}
   371  	for k, v := range a.LimitedClientConfig() {
   372  		respCfg[k] = v
   373  	}
   374  
   375  	// These properties are not configurable, but nevertheless represent configuration expected
   376  	// by the client.
   377  	respCfg["NoAccounts"] = strconv.FormatBool(a.IsFirstUserAccount())
   378  
   379  	return respCfg
   380  }
   381  
   382  // GetConfigFile proxies access to the given configuration file to the underlying config store.
   383  func (a *App) GetConfigFile(name string) ([]byte, error) {
   384  	data, err := a.Srv().configStore.GetFile(name)
   385  	if err != nil {
   386  		return nil, errors.Wrapf(err, "failed to get config file %s", name)
   387  	}
   388  
   389  	return data, nil
   390  }
   391  
   392  // GetSanitizedConfig gets the configuration for a system admin without any secrets.
   393  func (a *App) GetSanitizedConfig() *model.Config {
   394  	cfg := a.Config().Clone()
   395  	cfg.Sanitize()
   396  
   397  	return cfg
   398  }
   399  
   400  // GetEnvironmentConfig returns a map of configuration keys whose values have been overridden by an environment variable.
   401  func (a *App) GetEnvironmentConfig() map[string]interface{} {
   402  	return a.EnvironmentConfig()
   403  }
   404  
   405  // SaveConfig replaces the active configuration, optionally notifying cluster peers.
   406  func (s *Server) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) *model.AppError {
   407  	oldCfg, err := s.configStore.Set(newCfg)
   408  	if errors.Cause(err) == config.ErrReadOnlyConfiguration {
   409  		return model.NewAppError("saveConfig", "ent.cluster.save_config.error", nil, err.Error(), http.StatusForbidden)
   410  	} else if err != nil {
   411  		return model.NewAppError("saveConfig", "app.save_config.app_error", nil, err.Error(), http.StatusInternalServerError)
   412  	}
   413  
   414  	if s.Metrics != nil {
   415  		if *s.Config().MetricsSettings.Enable {
   416  			s.Metrics.StartServer()
   417  		} else {
   418  			s.Metrics.StopServer()
   419  		}
   420  	}
   421  
   422  	if s.Cluster != nil {
   423  		newCfg = s.configStore.RemoveEnvironmentOverrides(newCfg)
   424  		oldCfg = s.configStore.RemoveEnvironmentOverrides(oldCfg)
   425  		err := s.Cluster.ConfigChanged(oldCfg, newCfg, sendConfigChangeClusterMessage)
   426  		if err != nil {
   427  			return err
   428  		}
   429  	}
   430  
   431  	return nil
   432  }
   433  
   434  // SaveConfig replaces the active configuration, optionally notifying cluster peers.
   435  func (a *App) SaveConfig(newCfg *model.Config, sendConfigChangeClusterMessage bool) *model.AppError {
   436  	return a.Srv().SaveConfig(newCfg, sendConfigChangeClusterMessage)
   437  }
   438  
   439  func (a *App) HandleMessageExportConfig(cfg *model.Config, appCfg *model.Config) {
   440  	// If the Message Export feature has been toggled in the System Console, rewrite the ExportFromTimestamp field to an
   441  	// appropriate value. The rewriting occurs here to ensure it doesn't affect values written to the config file
   442  	// directly and not through the System Console UI.
   443  	if *cfg.MessageExportSettings.EnableExport != *appCfg.MessageExportSettings.EnableExport {
   444  		if *cfg.MessageExportSettings.EnableExport && *cfg.MessageExportSettings.ExportFromTimestamp == int64(0) {
   445  			// When the feature is toggled on, use the current timestamp as the start time for future exports.
   446  			cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(model.GetMillis())
   447  		} else if !*cfg.MessageExportSettings.EnableExport {
   448  			// When the feature is disabled, reset the timestamp so that the timestamp will be set if
   449  			// the feature is re-enabled from the System Console in future.
   450  			cfg.MessageExportSettings.ExportFromTimestamp = model.NewInt64(0)
   451  		}
   452  	}
   453  }
   454  
   455  func (s *Server) MailServiceConfig() *mailservice.SMTPConfig {
   456  	emailSettings := s.Config().EmailSettings
   457  	hostname := utils.GetHostnameFromSiteURL(*s.Config().ServiceSettings.SiteURL)
   458  	cfg := mailservice.SMTPConfig{
   459  		Hostname:                          hostname,
   460  		ConnectionSecurity:                *emailSettings.ConnectionSecurity,
   461  		SkipServerCertificateVerification: *emailSettings.SkipServerCertificateVerification,
   462  		ServerName:                        *emailSettings.SMTPServer,
   463  		Server:                            *emailSettings.SMTPServer,
   464  		Port:                              *emailSettings.SMTPPort,
   465  		ServerTimeout:                     *emailSettings.SMTPServerTimeout,
   466  		Username:                          *emailSettings.SMTPUsername,
   467  		Password:                          *emailSettings.SMTPPassword,
   468  		EnableSMTPAuth:                    *emailSettings.EnableSMTPAuth,
   469  		SendEmailNotifications:            *emailSettings.SendEmailNotifications,
   470  		FeedbackName:                      *emailSettings.FeedbackName,
   471  		FeedbackEmail:                     *emailSettings.FeedbackEmail,
   472  		ReplyToAddress:                    *emailSettings.ReplyToAddress,
   473  	}
   474  	return &cfg
   475  }