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  }