github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/app/app.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 "html/template" 9 "net" 10 "net/http" 11 "strings" 12 "sync" 13 "sync/atomic" 14 15 l4g "github.com/alecthomas/log4go" 16 "github.com/gorilla/mux" 17 "github.com/pkg/errors" 18 19 "github.com/mattermost/mattermost-server/einterfaces" 20 ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" 21 "github.com/mattermost/mattermost-server/jobs" 22 "github.com/mattermost/mattermost-server/model" 23 "github.com/mattermost/mattermost-server/plugin/pluginenv" 24 "github.com/mattermost/mattermost-server/store" 25 "github.com/mattermost/mattermost-server/store/sqlstore" 26 "github.com/mattermost/mattermost-server/utils" 27 ) 28 29 type App struct { 30 goroutineCount int32 31 goroutineExitSignal chan struct{} 32 33 Srv *Server 34 35 PluginEnv *pluginenv.Environment 36 PluginConfigListenerId string 37 38 EmailBatching *EmailBatchingJob 39 40 Hubs []*Hub 41 HubsStopCheckingForDeadlock chan bool 42 43 Jobs *jobs.JobServer 44 45 AccountMigration einterfaces.AccountMigrationInterface 46 Brand einterfaces.BrandInterface 47 Cluster einterfaces.ClusterInterface 48 Compliance einterfaces.ComplianceInterface 49 DataRetention einterfaces.DataRetentionInterface 50 Elasticsearch einterfaces.ElasticsearchInterface 51 Emoji einterfaces.EmojiInterface 52 Ldap einterfaces.LdapInterface 53 MessageExport einterfaces.MessageExportInterface 54 Metrics einterfaces.MetricsInterface 55 Mfa einterfaces.MfaInterface 56 Saml einterfaces.SamlInterface 57 58 config atomic.Value 59 configFile string 60 configListeners map[string]func(*model.Config, *model.Config) 61 62 licenseValue atomic.Value 63 clientLicenseValue atomic.Value 64 licenseListeners map[string]func() 65 66 siteURL string 67 68 newStore func() store.Store 69 70 htmlTemplateWatcher *utils.HTMLTemplateWatcher 71 sessionCache *utils.Cache 72 roles map[string]*model.Role 73 configListenerId string 74 licenseListenerId string 75 disableConfigWatch bool 76 configWatcher *utils.ConfigWatcher 77 asymmetricSigningKey *ecdsa.PrivateKey 78 79 pluginCommands []*PluginCommand 80 pluginCommandsLock sync.RWMutex 81 82 clientConfig map[string]string 83 clientConfigHash string 84 diagnosticId string 85 } 86 87 var appCount = 0 88 89 // New creates a new App. You must call Shutdown when you're done with it. 90 // XXX: For now, only one at a time is allowed as some resources are still shared. 91 func New(options ...Option) (outApp *App, outErr error) { 92 appCount++ 93 if appCount > 1 { 94 panic("Only one App should exist at a time. Did you forget to call Shutdown()?") 95 } 96 97 app := &App{ 98 goroutineExitSignal: make(chan struct{}, 1), 99 Srv: &Server{ 100 Router: mux.NewRouter(), 101 }, 102 sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE), 103 configFile: "config.json", 104 configListeners: make(map[string]func(*model.Config, *model.Config)), 105 clientConfig: make(map[string]string), 106 licenseListeners: map[string]func(){}, 107 } 108 defer func() { 109 if outErr != nil { 110 app.Shutdown() 111 } 112 }() 113 114 for _, option := range options { 115 option(app) 116 } 117 118 if utils.T == nil { 119 if err := utils.TranslationsPreInit(); err != nil { 120 return nil, errors.Wrapf(err, "unable to load Mattermost translation files") 121 } 122 } 123 model.AppErrorInit(utils.T) 124 if err := app.LoadConfig(app.configFile); err != nil { 125 return nil, err 126 } 127 app.EnableConfigWatch() 128 if err := utils.InitTranslations(app.Config().LocalizationSettings); err != nil { 129 return nil, errors.Wrapf(err, "unable to load Mattermost translation files") 130 } 131 132 app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) { 133 app.configOrLicenseListener() 134 135 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil) 136 137 message.Add("config", app.ClientConfigWithNoAccounts()) 138 app.Go(func() { 139 app.Publish(message) 140 }) 141 }) 142 app.licenseListenerId = app.AddLicenseListener(func() { 143 app.configOrLicenseListener() 144 145 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil) 146 message.Add("license", app.GetSanitizedClientLicense()) 147 app.Go(func() { 148 app.Publish(message) 149 }) 150 151 }) 152 app.regenerateClientConfig() 153 app.setDefaultRolesBasedOnConfig() 154 155 l4g.Info(utils.T("api.server.new_server.init.info")) 156 157 app.initEnterprise() 158 159 if app.newStore == nil { 160 app.newStore = func() store.Store { 161 return store.NewLayeredStore(sqlstore.NewSqlSupplier(app.Config().SqlSettings, app.Metrics), app.Metrics, app.Cluster) 162 } 163 } 164 165 if htmlTemplateWatcher, err := utils.NewHTMLTemplateWatcher("templates"); err != nil { 166 l4g.Error(utils.T("api.api.init.parsing_templates.error"), err) 167 } else { 168 app.htmlTemplateWatcher = htmlTemplateWatcher 169 } 170 171 app.Srv.Store = app.newStore() 172 if err := app.ensureAsymmetricSigningKey(); err != nil { 173 return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key") 174 } 175 176 app.initJobs() 177 178 app.initBuiltInPlugins() 179 app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}", app.ServePluginRequest) 180 app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", app.ServePluginRequest) 181 182 app.Srv.Router.NotFoundHandler = http.HandlerFunc(app.Handle404) 183 184 app.Srv.WebSocketRouter = &WebSocketRouter{ 185 app: app, 186 handlers: make(map[string]webSocketHandler), 187 } 188 189 return app, nil 190 } 191 192 func (a *App) configOrLicenseListener() { 193 a.regenerateClientConfig() 194 a.setDefaultRolesBasedOnConfig() 195 } 196 197 func (a *App) Shutdown() { 198 appCount-- 199 200 l4g.Info(utils.T("api.server.stop_server.stopping.info")) 201 202 a.StopServer() 203 a.HubStop() 204 205 a.ShutDownPlugins() 206 a.WaitForGoroutines() 207 208 if a.Srv.Store != nil { 209 a.Srv.Store.Close() 210 } 211 a.Srv = nil 212 213 if a.htmlTemplateWatcher != nil { 214 a.htmlTemplateWatcher.Close() 215 } 216 217 a.RemoveConfigListener(a.configListenerId) 218 a.RemoveLicenseListener(a.licenseListenerId) 219 l4g.Info(utils.T("api.server.stop_server.stopped.info")) 220 221 a.DisableConfigWatch() 222 } 223 224 var accountMigrationInterface func(*App) einterfaces.AccountMigrationInterface 225 226 func RegisterAccountMigrationInterface(f func(*App) einterfaces.AccountMigrationInterface) { 227 accountMigrationInterface = f 228 } 229 230 var brandInterface func(*App) einterfaces.BrandInterface 231 232 func RegisterBrandInterface(f func(*App) einterfaces.BrandInterface) { 233 brandInterface = f 234 } 235 236 var clusterInterface func(*App) einterfaces.ClusterInterface 237 238 func RegisterClusterInterface(f func(*App) einterfaces.ClusterInterface) { 239 clusterInterface = f 240 } 241 242 var complianceInterface func(*App) einterfaces.ComplianceInterface 243 244 func RegisterComplianceInterface(f func(*App) einterfaces.ComplianceInterface) { 245 complianceInterface = f 246 } 247 248 var dataRetentionInterface func(*App) einterfaces.DataRetentionInterface 249 250 func RegisterDataRetentionInterface(f func(*App) einterfaces.DataRetentionInterface) { 251 dataRetentionInterface = f 252 } 253 254 var elasticsearchInterface func(*App) einterfaces.ElasticsearchInterface 255 256 func RegisterElasticsearchInterface(f func(*App) einterfaces.ElasticsearchInterface) { 257 elasticsearchInterface = f 258 } 259 260 var emojiInterface func(*App) einterfaces.EmojiInterface 261 262 func RegisterEmojiInterface(f func(*App) einterfaces.EmojiInterface) { 263 emojiInterface = f 264 } 265 266 var jobsDataRetentionJobInterface func(*App) ejobs.DataRetentionJobInterface 267 268 func RegisterJobsDataRetentionJobInterface(f func(*App) ejobs.DataRetentionJobInterface) { 269 jobsDataRetentionJobInterface = f 270 } 271 272 var jobsMessageExportJobInterface func(*App) ejobs.MessageExportJobInterface 273 274 func RegisterJobsMessageExportJobInterface(f func(*App) ejobs.MessageExportJobInterface) { 275 jobsMessageExportJobInterface = f 276 } 277 278 var jobsElasticsearchAggregatorInterface func(*App) ejobs.ElasticsearchAggregatorInterface 279 280 func RegisterJobsElasticsearchAggregatorInterface(f func(*App) ejobs.ElasticsearchAggregatorInterface) { 281 jobsElasticsearchAggregatorInterface = f 282 } 283 284 var jobsElasticsearchIndexerInterface func(*App) ejobs.ElasticsearchIndexerInterface 285 286 func RegisterJobsElasticsearchIndexerInterface(f func(*App) ejobs.ElasticsearchIndexerInterface) { 287 jobsElasticsearchIndexerInterface = f 288 } 289 290 var jobsLdapSyncInterface func(*App) ejobs.LdapSyncInterface 291 292 func RegisterJobsLdapSyncInterface(f func(*App) ejobs.LdapSyncInterface) { 293 jobsLdapSyncInterface = f 294 } 295 296 var ldapInterface func(*App) einterfaces.LdapInterface 297 298 func RegisterLdapInterface(f func(*App) einterfaces.LdapInterface) { 299 ldapInterface = f 300 } 301 302 var messageExportInterface func(*App) einterfaces.MessageExportInterface 303 304 func RegisterMessageExportInterface(f func(*App) einterfaces.MessageExportInterface) { 305 messageExportInterface = f 306 } 307 308 var metricsInterface func(*App) einterfaces.MetricsInterface 309 310 func RegisterMetricsInterface(f func(*App) einterfaces.MetricsInterface) { 311 metricsInterface = f 312 } 313 314 var mfaInterface func(*App) einterfaces.MfaInterface 315 316 func RegisterMfaInterface(f func(*App) einterfaces.MfaInterface) { 317 mfaInterface = f 318 } 319 320 var samlInterface func(*App) einterfaces.SamlInterface 321 322 func RegisterSamlInterface(f func(*App) einterfaces.SamlInterface) { 323 samlInterface = f 324 } 325 326 func (a *App) initEnterprise() { 327 if accountMigrationInterface != nil { 328 a.AccountMigration = accountMigrationInterface(a) 329 } 330 if brandInterface != nil { 331 a.Brand = brandInterface(a) 332 } 333 if clusterInterface != nil { 334 a.Cluster = clusterInterface(a) 335 } 336 if complianceInterface != nil { 337 a.Compliance = complianceInterface(a) 338 } 339 if elasticsearchInterface != nil { 340 a.Elasticsearch = elasticsearchInterface(a) 341 } 342 if emojiInterface != nil { 343 a.Emoji = emojiInterface(a) 344 } 345 if ldapInterface != nil { 346 a.Ldap = ldapInterface(a) 347 a.AddConfigListener(func(_, cfg *model.Config) { 348 if err := utils.ValidateLdapFilter(cfg, a.Ldap); err != nil { 349 panic(utils.T(err.Id)) 350 } 351 }) 352 } 353 if messageExportInterface != nil { 354 a.MessageExport = messageExportInterface(a) 355 } 356 if metricsInterface != nil { 357 a.Metrics = metricsInterface(a) 358 } 359 if mfaInterface != nil { 360 a.Mfa = mfaInterface(a) 361 } 362 if samlInterface != nil { 363 a.Saml = samlInterface(a) 364 a.AddConfigListener(func(_, cfg *model.Config) { 365 a.Saml.ConfigureSP() 366 }) 367 } 368 if dataRetentionInterface != nil { 369 a.DataRetention = dataRetentionInterface(a) 370 } 371 } 372 373 func (a *App) initJobs() { 374 a.Jobs = jobs.NewJobServer(a, a.Srv.Store) 375 if jobsDataRetentionJobInterface != nil { 376 a.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(a) 377 } 378 if jobsMessageExportJobInterface != nil { 379 a.Jobs.MessageExportJob = jobsMessageExportJobInterface(a) 380 } 381 if jobsElasticsearchAggregatorInterface != nil { 382 a.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(a) 383 } 384 if jobsElasticsearchIndexerInterface != nil { 385 a.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(a) 386 } 387 if jobsLdapSyncInterface != nil { 388 a.Jobs.LdapSync = jobsLdapSyncInterface(a) 389 } 390 } 391 392 func (a *App) DiagnosticId() string { 393 return a.diagnosticId 394 } 395 396 func (a *App) SetDiagnosticId(id string) { 397 a.diagnosticId = id 398 } 399 400 func (a *App) EnsureDiagnosticId() { 401 if a.diagnosticId != "" { 402 return 403 } 404 if result := <-a.Srv.Store.System().Get(); result.Err == nil { 405 props := result.Data.(model.StringMap) 406 407 id := props[model.SYSTEM_DIAGNOSTIC_ID] 408 if len(id) == 0 { 409 id = model.NewId() 410 systemId := &model.System{Name: model.SYSTEM_DIAGNOSTIC_ID, Value: id} 411 <-a.Srv.Store.System().Save(systemId) 412 } 413 414 a.diagnosticId = id 415 } 416 } 417 418 // Go creates a goroutine, but maintains a record of it to ensure that execution completes before 419 // the app is destroyed. 420 func (a *App) Go(f func()) { 421 atomic.AddInt32(&a.goroutineCount, 1) 422 423 go func() { 424 f() 425 426 atomic.AddInt32(&a.goroutineCount, -1) 427 select { 428 case a.goroutineExitSignal <- struct{}{}: 429 default: 430 } 431 }() 432 } 433 434 // WaitForGoroutines blocks until all goroutines created by App.Go exit. 435 func (a *App) WaitForGoroutines() { 436 for atomic.LoadInt32(&a.goroutineCount) != 0 { 437 <-a.goroutineExitSignal 438 } 439 } 440 441 func (a *App) HTMLTemplates() *template.Template { 442 if a.htmlTemplateWatcher != nil { 443 return a.htmlTemplateWatcher.Templates() 444 } 445 446 return nil 447 } 448 449 func (a *App) HTTPClient(trustURLs bool) *http.Client { 450 insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections != nil && *a.Config().ServiceSettings.EnableInsecureOutgoingConnections 451 452 if trustURLs { 453 return utils.NewHTTPClient(insecure, nil, nil) 454 } 455 456 allowHost := func(host string) bool { 457 if a.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil { 458 return false 459 } 460 for _, allowed := range strings.Fields(*a.Config().ServiceSettings.AllowedUntrustedInternalConnections) { 461 if host == allowed { 462 return true 463 } 464 } 465 return false 466 } 467 468 allowIP := func(ip net.IP) bool { 469 if !utils.IsReservedIP(ip) { 470 return true 471 } 472 if a.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil { 473 return false 474 } 475 for _, allowed := range strings.Fields(*a.Config().ServiceSettings.AllowedUntrustedInternalConnections) { 476 if _, ipRange, err := net.ParseCIDR(allowed); err == nil && ipRange.Contains(ip) { 477 return true 478 } 479 } 480 return false 481 } 482 483 return utils.NewHTTPClient(insecure, allowHost, allowIP) 484 } 485 486 func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { 487 err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) 488 489 l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) 490 491 utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) 492 }