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