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 }