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 }