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 }