2017-04-12 08:27:57 -04:00
|
|
|
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
2017-01-25 09:32:42 -05:00
|
|
|
// See License.txt for license information.
|
|
|
|
|
|
|
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
import (
|
2018-02-07 11:05:46 -06:00
|
|
|
"crypto/ecdsa"
|
2018-04-27 12:49:45 -07:00
|
|
|
"fmt"
|
2017-11-20 11:57:45 -06:00
|
|
|
"html/template"
|
2017-11-22 09:15:03 -06:00
|
|
|
"net"
|
2017-01-25 09:32:42 -05:00
|
|
|
"net/http"
|
2018-06-21 14:31:51 -04:00
|
|
|
"path"
|
2018-02-06 15:34:08 +00:00
|
|
|
"reflect"
|
2017-11-22 09:15:03 -06:00
|
|
|
"strings"
|
2017-12-08 13:55:41 -06:00
|
|
|
"sync"
|
2017-10-03 10:53:53 -05:00
|
|
|
"sync/atomic"
|
|
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
"github.com/gorilla/mux"
|
2018-01-11 15:23:41 -06:00
|
|
|
"github.com/pkg/errors"
|
2017-09-11 10:02:02 -05:00
|
|
|
|
2017-09-19 18:31:35 -05:00
|
|
|
"github.com/mattermost/mattermost-server/einterfaces"
|
2017-09-29 04:29:29 -05:00
|
|
|
ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs"
|
|
|
|
|
"github.com/mattermost/mattermost-server/jobs"
|
2018-05-14 15:59:04 +01:00
|
|
|
tjobs "github.com/mattermost/mattermost-server/jobs/interfaces"
|
2018-04-27 12:49:45 -07:00
|
|
|
"github.com/mattermost/mattermost-server/mlog"
|
2017-09-21 04:13:34 -05:00
|
|
|
"github.com/mattermost/mattermost-server/model"
|
2018-06-25 12:33:13 -07:00
|
|
|
"github.com/mattermost/mattermost-server/plugin"
|
2017-10-09 14:59:48 -07:00
|
|
|
"github.com/mattermost/mattermost-server/store"
|
|
|
|
|
"github.com/mattermost/mattermost-server/store/sqlstore"
|
2017-09-21 04:13:34 -05:00
|
|
|
"github.com/mattermost/mattermost-server/utils"
|
2017-01-25 09:32:42 -05:00
|
|
|
)
|
|
|
|
|
|
2018-02-06 15:34:08 +00:00
|
|
|
const ADVANCED_PERMISSIONS_MIGRATION_KEY = "AdvancedPermissionsMigrationComplete"
|
2018-05-29 16:58:12 +02:00
|
|
|
const EMOJIS_PERMISSIONS_MIGRATION_KEY = "EmojisPermissionsMigrationComplete"
|
2018-02-06 15:34:08 +00:00
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
type App struct {
|
2017-10-03 10:53:53 -05:00
|
|
|
goroutineCount int32
|
|
|
|
|
goroutineExitSignal chan struct{}
|
|
|
|
|
|
2017-09-19 18:31:35 -05:00
|
|
|
Srv *Server
|
|
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
Log *mlog.Logger
|
|
|
|
|
|
2018-06-25 12:33:13 -07:00
|
|
|
Plugins *plugin.Environment
|
|
|
|
|
PluginConfigListenerId string
|
2017-09-19 18:31:35 -05:00
|
|
|
|
2017-09-27 11:52:34 -05:00
|
|
|
EmailBatching *EmailBatchingJob
|
|
|
|
|
|
|
|
|
|
Hubs []*Hub
|
|
|
|
|
HubsStopCheckingForDeadlock chan bool
|
|
|
|
|
|
2017-09-29 04:29:29 -05:00
|
|
|
Jobs *jobs.JobServer
|
|
|
|
|
|
2017-09-19 18:31:35 -05:00
|
|
|
AccountMigration einterfaces.AccountMigrationInterface
|
|
|
|
|
Cluster einterfaces.ClusterInterface
|
|
|
|
|
Compliance einterfaces.ComplianceInterface
|
2017-10-02 12:43:21 +01:00
|
|
|
DataRetention einterfaces.DataRetentionInterface
|
2017-09-19 18:31:35 -05:00
|
|
|
Elasticsearch einterfaces.ElasticsearchInterface
|
|
|
|
|
Ldap einterfaces.LdapInterface
|
2017-11-30 09:07:04 -05:00
|
|
|
MessageExport einterfaces.MessageExportInterface
|
2017-09-19 18:31:35 -05:00
|
|
|
Metrics einterfaces.MetricsInterface
|
|
|
|
|
Mfa einterfaces.MfaInterface
|
|
|
|
|
Saml einterfaces.SamlInterface
|
2017-10-09 14:59:48 -07:00
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
config atomic.Value
|
2018-04-09 12:16:11 -04:00
|
|
|
envConfig map[string]interface{}
|
2018-01-12 08:02:11 -06:00
|
|
|
configFile string
|
|
|
|
|
configListeners map[string]func(*model.Config, *model.Config)
|
|
|
|
|
|
2018-02-09 10:04:48 -06:00
|
|
|
licenseValue atomic.Value
|
|
|
|
|
clientLicenseValue atomic.Value
|
|
|
|
|
licenseListeners map[string]func()
|
|
|
|
|
|
2018-03-22 06:53:43 -07:00
|
|
|
timezones atomic.Value
|
|
|
|
|
|
2018-02-22 18:23:32 -06:00
|
|
|
siteURL string
|
|
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
newStore func() store.Store
|
2017-10-26 14:21:22 -05:00
|
|
|
|
2018-02-07 11:05:46 -06:00
|
|
|
htmlTemplateWatcher *utils.HTMLTemplateWatcher
|
|
|
|
|
sessionCache *utils.Cache
|
|
|
|
|
configListenerId string
|
|
|
|
|
licenseListenerId string
|
2018-04-27 12:49:45 -07:00
|
|
|
logListenerId string
|
2018-02-07 11:05:46 -06:00
|
|
|
disableConfigWatch bool
|
|
|
|
|
configWatcher *utils.ConfigWatcher
|
|
|
|
|
asymmetricSigningKey *ecdsa.PrivateKey
|
2017-12-08 13:55:41 -06:00
|
|
|
|
|
|
|
|
pluginCommands []*PluginCommand
|
|
|
|
|
pluginCommandsLock sync.RWMutex
|
2018-01-05 16:17:57 -06:00
|
|
|
|
2018-06-18 12:39:22 -04:00
|
|
|
clientConfig map[string]string
|
|
|
|
|
clientConfigHash string
|
|
|
|
|
limitedClientConfig map[string]string
|
|
|
|
|
diagnosticId string
|
2018-05-21 06:10:26 -04:00
|
|
|
|
|
|
|
|
phase2PermissionsMigrationComplete bool
|
2017-09-06 17:12:54 -05:00
|
|
|
}
|
|
|
|
|
|
2017-10-02 03:50:56 -05:00
|
|
|
var appCount = 0
|
|
|
|
|
|
|
|
|
|
// New creates a new App. You must call Shutdown when you're done with it.
|
2017-10-04 13:09:41 -07:00
|
|
|
// XXX: For now, only one at a time is allowed as some resources are still shared.
|
2018-02-12 22:16:32 +05:30
|
|
|
func New(options ...Option) (outApp *App, outErr error) {
|
2017-10-02 03:50:56 -05:00
|
|
|
appCount++
|
2017-10-04 13:09:41 -07:00
|
|
|
if appCount > 1 {
|
|
|
|
|
panic("Only one App should exist at a time. Did you forget to call Shutdown()?")
|
2017-10-02 03:50:56 -05:00
|
|
|
}
|
|
|
|
|
|
2018-06-21 14:31:51 -04:00
|
|
|
rootRouter := mux.NewRouter()
|
|
|
|
|
|
2017-10-04 13:09:41 -07:00
|
|
|
app := &App{
|
|
|
|
|
goroutineExitSignal: make(chan struct{}, 1),
|
2017-10-09 14:59:48 -07:00
|
|
|
Srv: &Server{
|
2018-06-21 14:31:51 -04:00
|
|
|
RootRouter: rootRouter,
|
2017-10-09 14:59:48 -07:00
|
|
|
},
|
2018-02-09 10:04:48 -06:00
|
|
|
sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE),
|
|
|
|
|
configFile: "config.json",
|
|
|
|
|
configListeners: make(map[string]func(*model.Config, *model.Config)),
|
|
|
|
|
clientConfig: make(map[string]string),
|
|
|
|
|
licenseListeners: map[string]func(){},
|
2017-10-04 13:09:41 -07:00
|
|
|
}
|
2018-02-12 22:16:32 +05:30
|
|
|
defer func() {
|
|
|
|
|
if outErr != nil {
|
|
|
|
|
app.Shutdown()
|
|
|
|
|
}
|
|
|
|
|
}()
|
2017-10-09 14:59:48 -07:00
|
|
|
|
|
|
|
|
for _, option := range options {
|
|
|
|
|
option(app)
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-16 08:40:26 -06:00
|
|
|
if utils.T == nil {
|
2018-01-11 15:23:41 -06:00
|
|
|
if err := utils.TranslationsPreInit(); err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "unable to load Mattermost translation files")
|
|
|
|
|
}
|
2017-11-16 08:40:26 -06:00
|
|
|
}
|
2018-01-08 19:13:24 +01:00
|
|
|
model.AppErrorInit(utils.T)
|
2018-04-13 20:09:38 -04:00
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
if err := app.LoadConfig(app.configFile); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-04-27 12:49:45 -07:00
|
|
|
|
|
|
|
|
// Initalize logging
|
|
|
|
|
app.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&app.Config().LogSettings))
|
|
|
|
|
|
|
|
|
|
// Redirect default golang logger to this logger
|
|
|
|
|
mlog.RedirectStdLog(app.Log)
|
|
|
|
|
|
|
|
|
|
// Use this app logger as the global logger (eventually remove all instances of global logging)
|
|
|
|
|
mlog.InitGlobalLogger(app.Log)
|
|
|
|
|
|
|
|
|
|
app.logListenerId = app.AddConfigListener(func(_, after *model.Config) {
|
|
|
|
|
app.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings))
|
|
|
|
|
})
|
|
|
|
|
|
2018-01-11 15:23:41 -06:00
|
|
|
app.EnableConfigWatch()
|
2018-03-22 06:53:43 -07:00
|
|
|
|
|
|
|
|
app.LoadTimezones()
|
|
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
if err := utils.InitTranslations(app.Config().LocalizationSettings); err != nil {
|
2018-01-11 15:23:41 -06:00
|
|
|
return nil, errors.Wrapf(err, "unable to load Mattermost translation files")
|
|
|
|
|
}
|
2017-11-16 08:40:26 -06:00
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
|
2018-01-05 16:17:57 -06:00
|
|
|
app.configOrLicenseListener()
|
2018-03-05 07:18:22 -05:00
|
|
|
|
|
|
|
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil)
|
|
|
|
|
|
Relax 4k post message limit (#8478)
* MM-9661: rename POST_MESSAGE_MAX_RUNES to \0_v1
* MM-9661: s/4000/POST_MESSAGE_MAX_RUNES_V1/ in tests
* MM-9661: introduce POST_MESSAGE_MAX_RUNES_V2
* MM-9661: migrate Postgres Posts.Message column to TEXT from VARCHAR(4000)
This is safe to do in a production instance since the underyling type is
not changing. We explicitly don't do this automatically for MySQL, but
also don't need to since the ORM would have already created a TEXT column
for MySQL in that case.
* MM-9661: emit MaxPostSize in client config
This value remains unconfigurable at this time, but exposes the current
limit to the client. The limit remains at 4k in this commit.
* MM-9661: introduce and use SqlPostStore.GetMaxPostSize
Enforce a byte limitation in the database, and use 1/4 of that value as
the rune count limitation (assuming a worst case UTF-8 representation).
* move maxPostSizeCached, lastPostsCache and lastPostTimeCache out of the global context and onto the SqlPostStore
* address feedback from code review:
* ensure sqlstore unit tests are actually being run
* move global caches into SqlPostStore
* leverage sync.Once to address a race condition
* modify upgrade semantics to match new db semantics
gorp's behaviour on creating columns with a maximum length on Postgres
differs from MySQL:
* Postgres
* gorp uses TEXT for string columns without a maximum length
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* MySQL
* gorp uses TEXT for string columns with a maximum length >= 256
* gorp uses VARCHAR(N) for string columns with a maximum length of N
* gorp defaults to a maximum length of 255, implying VARCHAR(255)
So the Message column has been TEXT on MySQL but VARCHAR(4000) on
Postgres. With the new, longer limits of 65535, and without changes to
gorp, the expected behaviour is TEXT on MySQL and VARCHAR(65535) on
Postgres. This commit makes the upgrade semantics match the new database
semantics.
Ideally, we'd revisit the gorp behaviour at a later time.
* allow TestMaxPostSize test cases to actually run in parallel
* default maxPostSizeCached to POST_MESSAGE_MAX_RUNES_V1 in case the once initializer panics
* fix casting error
* MM-9661: skip the schema migration for Postgres
It turns out resizing VARCHAR requires a rewrite in some versions of
Postgres, but migrating VARCHAR to TEXT does not. Given the increasing
complexity, let's defer the migration to the enduser instead.
2018-03-26 17:55:35 -04:00
|
|
|
message.Add("config", app.ClientConfigWithComputed())
|
2018-03-05 07:18:22 -05:00
|
|
|
app.Go(func() {
|
|
|
|
|
app.Publish(message)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
app.licenseListenerId = app.AddLicenseListener(func() {
|
|
|
|
|
app.configOrLicenseListener()
|
|
|
|
|
|
|
|
|
|
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil)
|
|
|
|
|
message.Add("license", app.GetSanitizedClientLicense())
|
|
|
|
|
app.Go(func() {
|
|
|
|
|
app.Publish(message)
|
|
|
|
|
})
|
|
|
|
|
|
2017-11-21 13:08:32 -06:00
|
|
|
})
|
2018-01-05 16:17:57 -06:00
|
|
|
app.regenerateClientConfig()
|
2017-11-21 13:08:32 -06:00
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Info("Server is initializing...")
|
2017-11-16 08:40:26 -06:00
|
|
|
|
|
|
|
|
app.initEnterprise()
|
|
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
if app.newStore == nil {
|
|
|
|
|
app.newStore = func() store.Store {
|
2017-10-16 08:09:43 -07:00
|
|
|
return store.NewLayeredStore(sqlstore.NewSqlSupplier(app.Config().SqlSettings, app.Metrics), app.Metrics, app.Cluster)
|
2017-10-09 14:59:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-20 11:57:45 -06:00
|
|
|
if htmlTemplateWatcher, err := utils.NewHTMLTemplateWatcher("templates"); err != nil {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Error(fmt.Sprintf("Failed to parse server templates %v", err))
|
2017-11-20 11:57:45 -06:00
|
|
|
} else {
|
|
|
|
|
app.htmlTemplateWatcher = htmlTemplateWatcher
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
app.Srv.Store = app.newStore()
|
2018-02-07 11:05:46 -06:00
|
|
|
if err := app.ensureAsymmetricSigningKey(); err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key")
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-26 14:21:22 -05:00
|
|
|
app.initJobs()
|
2018-06-26 18:30:33 +01:00
|
|
|
app.AddLicenseListener(func() {
|
|
|
|
|
app.initJobs()
|
|
|
|
|
})
|
2017-10-09 14:59:48 -07:00
|
|
|
|
2018-06-21 14:31:51 -04:00
|
|
|
subpath, err := utils.GetSubpathFromConfig(app.Config())
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to parse SiteURL subpath")
|
|
|
|
|
}
|
|
|
|
|
app.Srv.Router = app.Srv.RootRouter.PathPrefix(subpath).Subrouter()
|
2017-11-03 17:47:52 -04:00
|
|
|
app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}", app.ServePluginRequest)
|
|
|
|
|
app.Srv.Router.HandleFunc("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}", app.ServePluginRequest)
|
|
|
|
|
|
2018-06-21 14:31:51 -04:00
|
|
|
// If configured with a subpath, redirect 404s at the root back into the subpath.
|
|
|
|
|
if subpath != "/" {
|
|
|
|
|
app.Srv.RootRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
r.URL.Path = path.Join(subpath, r.URL.Path)
|
|
|
|
|
http.Redirect(w, r, r.URL.String(), http.StatusFound)
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-10-09 14:59:48 -07:00
|
|
|
app.Srv.Router.NotFoundHandler = http.HandlerFunc(app.Handle404)
|
|
|
|
|
|
|
|
|
|
app.Srv.WebSocketRouter = &WebSocketRouter{
|
|
|
|
|
app: app,
|
|
|
|
|
handlers: make(map[string]webSocketHandler),
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 15:23:41 -06:00
|
|
|
return app, nil
|
2017-09-06 17:12:54 -05:00
|
|
|
}
|
|
|
|
|
|
2018-01-05 16:17:57 -06:00
|
|
|
func (a *App) configOrLicenseListener() {
|
|
|
|
|
a.regenerateClientConfig()
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-02 03:50:56 -05:00
|
|
|
func (a *App) Shutdown() {
|
|
|
|
|
appCount--
|
2017-10-03 10:53:53 -05:00
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Info("Stopping Server...")
|
2017-10-04 13:09:41 -07:00
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
a.StopServer()
|
|
|
|
|
a.HubStop()
|
2017-10-03 10:53:53 -05:00
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
a.ShutDownPlugins()
|
|
|
|
|
a.WaitForGoroutines()
|
2017-10-03 10:53:53 -05:00
|
|
|
|
2018-02-12 22:16:32 +05:30
|
|
|
if a.Srv.Store != nil {
|
|
|
|
|
a.Srv.Store.Close()
|
|
|
|
|
}
|
2017-10-09 14:59:48 -07:00
|
|
|
a.Srv = nil
|
2017-10-03 10:53:53 -05:00
|
|
|
|
2017-11-20 11:57:45 -06:00
|
|
|
if a.htmlTemplateWatcher != nil {
|
|
|
|
|
a.htmlTemplateWatcher.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-12 08:02:11 -06:00
|
|
|
a.RemoveConfigListener(a.configListenerId)
|
2018-02-09 10:04:48 -06:00
|
|
|
a.RemoveLicenseListener(a.licenseListenerId)
|
2018-04-27 12:49:45 -07:00
|
|
|
a.RemoveConfigListener(a.logListenerId)
|
|
|
|
|
mlog.Info("Server stopped")
|
2018-01-11 15:23:41 -06:00
|
|
|
|
|
|
|
|
a.DisableConfigWatch()
|
2017-10-02 03:50:56 -05:00
|
|
|
}
|
|
|
|
|
|
2017-09-21 04:13:34 -05:00
|
|
|
var accountMigrationInterface func(*App) einterfaces.AccountMigrationInterface
|
|
|
|
|
|
|
|
|
|
func RegisterAccountMigrationInterface(f func(*App) einterfaces.AccountMigrationInterface) {
|
|
|
|
|
accountMigrationInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var clusterInterface func(*App) einterfaces.ClusterInterface
|
|
|
|
|
|
|
|
|
|
func RegisterClusterInterface(f func(*App) einterfaces.ClusterInterface) {
|
|
|
|
|
clusterInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var complianceInterface func(*App) einterfaces.ComplianceInterface
|
|
|
|
|
|
|
|
|
|
func RegisterComplianceInterface(f func(*App) einterfaces.ComplianceInterface) {
|
|
|
|
|
complianceInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-02 12:43:21 +01:00
|
|
|
var dataRetentionInterface func(*App) einterfaces.DataRetentionInterface
|
2017-09-29 04:29:29 -05:00
|
|
|
|
2017-10-02 12:43:21 +01:00
|
|
|
func RegisterDataRetentionInterface(f func(*App) einterfaces.DataRetentionInterface) {
|
|
|
|
|
dataRetentionInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-16 15:04:27 -06:00
|
|
|
var elasticsearchInterface func(*App) einterfaces.ElasticsearchInterface
|
|
|
|
|
|
|
|
|
|
func RegisterElasticsearchInterface(f func(*App) einterfaces.ElasticsearchInterface) {
|
|
|
|
|
elasticsearchInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-02 12:43:21 +01:00
|
|
|
var jobsDataRetentionJobInterface func(*App) ejobs.DataRetentionJobInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsDataRetentionJobInterface(f func(*App) ejobs.DataRetentionJobInterface) {
|
|
|
|
|
jobsDataRetentionJobInterface = f
|
2017-09-29 04:29:29 -05:00
|
|
|
}
|
|
|
|
|
|
2017-11-30 09:07:04 -05:00
|
|
|
var jobsMessageExportJobInterface func(*App) ejobs.MessageExportJobInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsMessageExportJobInterface(f func(*App) ejobs.MessageExportJobInterface) {
|
|
|
|
|
jobsMessageExportJobInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-29 04:29:29 -05:00
|
|
|
var jobsElasticsearchAggregatorInterface func(*App) ejobs.ElasticsearchAggregatorInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsElasticsearchAggregatorInterface(f func(*App) ejobs.ElasticsearchAggregatorInterface) {
|
|
|
|
|
jobsElasticsearchAggregatorInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var jobsElasticsearchIndexerInterface func(*App) ejobs.ElasticsearchIndexerInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsElasticsearchIndexerInterface(f func(*App) ejobs.ElasticsearchIndexerInterface) {
|
|
|
|
|
jobsElasticsearchIndexerInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var jobsLdapSyncInterface func(*App) ejobs.LdapSyncInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsLdapSyncInterface(f func(*App) ejobs.LdapSyncInterface) {
|
|
|
|
|
jobsLdapSyncInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-14 15:59:04 +01:00
|
|
|
var jobsMigrationsInterface func(*App) tjobs.MigrationsJobInterface
|
|
|
|
|
|
|
|
|
|
func RegisterJobsMigrationsJobInterface(f func(*App) tjobs.MigrationsJobInterface) {
|
|
|
|
|
jobsMigrationsInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-21 04:13:34 -05:00
|
|
|
var ldapInterface func(*App) einterfaces.LdapInterface
|
|
|
|
|
|
|
|
|
|
func RegisterLdapInterface(f func(*App) einterfaces.LdapInterface) {
|
|
|
|
|
ldapInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-30 09:07:04 -05:00
|
|
|
var messageExportInterface func(*App) einterfaces.MessageExportInterface
|
|
|
|
|
|
|
|
|
|
func RegisterMessageExportInterface(f func(*App) einterfaces.MessageExportInterface) {
|
|
|
|
|
messageExportInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-21 04:13:34 -05:00
|
|
|
var metricsInterface func(*App) einterfaces.MetricsInterface
|
|
|
|
|
|
|
|
|
|
func RegisterMetricsInterface(f func(*App) einterfaces.MetricsInterface) {
|
|
|
|
|
metricsInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var mfaInterface func(*App) einterfaces.MfaInterface
|
|
|
|
|
|
|
|
|
|
func RegisterMfaInterface(f func(*App) einterfaces.MfaInterface) {
|
|
|
|
|
mfaInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var samlInterface func(*App) einterfaces.SamlInterface
|
|
|
|
|
|
|
|
|
|
func RegisterSamlInterface(f func(*App) einterfaces.SamlInterface) {
|
|
|
|
|
samlInterface = f
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) initEnterprise() {
|
|
|
|
|
if accountMigrationInterface != nil {
|
|
|
|
|
a.AccountMigration = accountMigrationInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if clusterInterface != nil {
|
|
|
|
|
a.Cluster = clusterInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if complianceInterface != nil {
|
|
|
|
|
a.Compliance = complianceInterface(a)
|
|
|
|
|
}
|
2017-11-16 15:04:27 -06:00
|
|
|
if elasticsearchInterface != nil {
|
|
|
|
|
a.Elasticsearch = elasticsearchInterface(a)
|
|
|
|
|
}
|
2017-09-21 04:13:34 -05:00
|
|
|
if ldapInterface != nil {
|
|
|
|
|
a.Ldap = ldapInterface(a)
|
2018-01-12 08:02:11 -06:00
|
|
|
a.AddConfigListener(func(_, cfg *model.Config) {
|
2017-09-21 04:13:34 -05:00
|
|
|
if err := utils.ValidateLdapFilter(cfg, a.Ldap); err != nil {
|
|
|
|
|
panic(utils.T(err.Id))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-11-30 09:07:04 -05:00
|
|
|
if messageExportInterface != nil {
|
|
|
|
|
a.MessageExport = messageExportInterface(a)
|
|
|
|
|
}
|
2017-09-21 04:13:34 -05:00
|
|
|
if metricsInterface != nil {
|
|
|
|
|
a.Metrics = metricsInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if mfaInterface != nil {
|
|
|
|
|
a.Mfa = mfaInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if samlInterface != nil {
|
|
|
|
|
a.Saml = samlInterface(a)
|
2018-01-12 08:02:11 -06:00
|
|
|
a.AddConfigListener(func(_, cfg *model.Config) {
|
2017-09-21 04:13:34 -05:00
|
|
|
a.Saml.ConfigureSP()
|
|
|
|
|
})
|
|
|
|
|
}
|
2017-10-02 12:43:21 +01:00
|
|
|
if dataRetentionInterface != nil {
|
|
|
|
|
a.DataRetention = dataRetentionInterface(a)
|
|
|
|
|
}
|
2017-10-26 14:21:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) initJobs() {
|
2018-01-12 08:02:11 -06:00
|
|
|
a.Jobs = jobs.NewJobServer(a, a.Srv.Store)
|
2017-10-02 12:43:21 +01:00
|
|
|
if jobsDataRetentionJobInterface != nil {
|
|
|
|
|
a.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(a)
|
2017-09-29 04:29:29 -05:00
|
|
|
}
|
2017-11-30 09:07:04 -05:00
|
|
|
if jobsMessageExportJobInterface != nil {
|
|
|
|
|
a.Jobs.MessageExportJob = jobsMessageExportJobInterface(a)
|
|
|
|
|
}
|
2017-09-29 04:29:29 -05:00
|
|
|
if jobsElasticsearchAggregatorInterface != nil {
|
|
|
|
|
a.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if jobsElasticsearchIndexerInterface != nil {
|
|
|
|
|
a.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(a)
|
|
|
|
|
}
|
|
|
|
|
if jobsLdapSyncInterface != nil {
|
|
|
|
|
a.Jobs.LdapSync = jobsLdapSyncInterface(a)
|
|
|
|
|
}
|
2018-05-14 15:59:04 +01:00
|
|
|
if jobsMigrationsInterface != nil {
|
|
|
|
|
a.Jobs.Migrations = jobsMigrationsInterface(a)
|
|
|
|
|
}
|
2017-09-21 04:13:34 -05:00
|
|
|
}
|
|
|
|
|
|
2018-01-05 16:17:57 -06:00
|
|
|
func (a *App) DiagnosticId() string {
|
|
|
|
|
return a.diagnosticId
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) SetDiagnosticId(id string) {
|
|
|
|
|
a.diagnosticId = id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (a *App) EnsureDiagnosticId() {
|
|
|
|
|
if a.diagnosticId != "" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if result := <-a.Srv.Store.System().Get(); result.Err == nil {
|
|
|
|
|
props := result.Data.(model.StringMap)
|
|
|
|
|
|
|
|
|
|
id := props[model.SYSTEM_DIAGNOSTIC_ID]
|
|
|
|
|
if len(id) == 0 {
|
|
|
|
|
id = model.NewId()
|
|
|
|
|
systemId := &model.System{Name: model.SYSTEM_DIAGNOSTIC_ID, Value: id}
|
|
|
|
|
<-a.Srv.Store.System().Save(systemId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a.diagnosticId = id
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-03 10:53:53 -05:00
|
|
|
// Go creates a goroutine, but maintains a record of it to ensure that execution completes before
|
|
|
|
|
// the app is destroyed.
|
|
|
|
|
func (a *App) Go(f func()) {
|
|
|
|
|
atomic.AddInt32(&a.goroutineCount, 1)
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
f()
|
|
|
|
|
|
|
|
|
|
atomic.AddInt32(&a.goroutineCount, -1)
|
|
|
|
|
select {
|
|
|
|
|
case a.goroutineExitSignal <- struct{}{}:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WaitForGoroutines blocks until all goroutines created by App.Go exit.
|
|
|
|
|
func (a *App) WaitForGoroutines() {
|
|
|
|
|
for atomic.LoadInt32(&a.goroutineCount) != 0 {
|
|
|
|
|
<-a.goroutineExitSignal
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-20 11:57:45 -06:00
|
|
|
func (a *App) HTMLTemplates() *template.Template {
|
2018-03-21 14:27:14 -04:00
|
|
|
if a.htmlTemplateWatcher != nil {
|
|
|
|
|
return a.htmlTemplateWatcher.Templates()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2017-11-20 11:57:45 -06:00
|
|
|
}
|
|
|
|
|
|
2017-11-22 09:15:03 -06:00
|
|
|
func (a *App) HTTPClient(trustURLs bool) *http.Client {
|
|
|
|
|
insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections != nil && *a.Config().ServiceSettings.EnableInsecureOutgoingConnections
|
|
|
|
|
|
|
|
|
|
if trustURLs {
|
|
|
|
|
return utils.NewHTTPClient(insecure, nil, nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allowHost := func(host string) bool {
|
|
|
|
|
if a.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, allowed := range strings.Fields(*a.Config().ServiceSettings.AllowedUntrustedInternalConnections) {
|
|
|
|
|
if host == allowed {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allowIP := func(ip net.IP) bool {
|
|
|
|
|
if !utils.IsReservedIP(ip) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if a.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
for _, allowed := range strings.Fields(*a.Config().ServiceSettings.AllowedUntrustedInternalConnections) {
|
|
|
|
|
if _, ipRange, err := net.ParseCIDR(allowed); err == nil && ipRange.Contains(ip) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return utils.NewHTTPClient(insecure, allowHost, allowIP)
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-09 14:59:48 -07:00
|
|
|
func (a *App) Handle404(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound)
|
|
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Debug(fmt.Sprintf("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)))
|
2017-10-09 14:59:48 -07:00
|
|
|
|
2018-06-21 14:31:51 -04:00
|
|
|
utils.RenderWebAppError(a.Config(), w, r, err, a.AsymmetricSigningKey())
|
2017-01-25 09:32:42 -05:00
|
|
|
}
|
2018-02-06 15:34:08 +00:00
|
|
|
|
|
|
|
|
// This function migrates the default built in roles from code/config to the database.
|
|
|
|
|
func (a *App) DoAdvancedPermissionsMigration() {
|
|
|
|
|
// If the migration is already marked as completed, don't do it again.
|
|
|
|
|
if result := <-a.Srv.Store.System().GetByName(ADVANCED_PERMISSIONS_MIGRATION_KEY); result.Err == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Info("Migrating roles to database.")
|
2018-02-06 15:34:08 +00:00
|
|
|
roles := model.MakeDefaultRoles()
|
2018-02-13 13:35:52 +00:00
|
|
|
roles = utils.SetRolePermissionsFromConfig(roles, a.Config(), a.License() != nil)
|
2018-02-06 15:34:08 +00:00
|
|
|
|
|
|
|
|
allSucceeded := true
|
|
|
|
|
|
|
|
|
|
for _, role := range roles {
|
|
|
|
|
if result := <-a.Srv.Store.Role().Save(role); result.Err != nil {
|
|
|
|
|
// If this failed for reasons other than the role already existing, don't mark the migration as done.
|
|
|
|
|
if result2 := <-a.Srv.Store.Role().GetByName(role.Name); result2.Err != nil {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Critical("Failed to migrate role to database.")
|
|
|
|
|
mlog.Critical(fmt.Sprint(result.Err))
|
2018-02-06 15:34:08 +00:00
|
|
|
allSucceeded = false
|
|
|
|
|
} else {
|
|
|
|
|
// If the role already existed, check it is the same and update if not.
|
|
|
|
|
fetchedRole := result.Data.(*model.Role)
|
|
|
|
|
if !reflect.DeepEqual(fetchedRole.Permissions, role.Permissions) ||
|
|
|
|
|
fetchedRole.DisplayName != role.DisplayName ||
|
|
|
|
|
fetchedRole.Description != role.Description ||
|
|
|
|
|
fetchedRole.SchemeManaged != role.SchemeManaged {
|
|
|
|
|
role.Id = fetchedRole.Id
|
|
|
|
|
if result := <-a.Srv.Store.Role().Save(role); result.Err != nil {
|
|
|
|
|
// Role is not the same, but failed to update.
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Critical("Failed to migrate role to database.")
|
|
|
|
|
mlog.Critical(fmt.Sprint(result.Err))
|
2018-02-06 15:34:08 +00:00
|
|
|
allSucceeded = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !allSucceeded {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-02 19:09:12 +01:00
|
|
|
config := a.Config()
|
|
|
|
|
if *config.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_ALWAYS {
|
|
|
|
|
*config.ServiceSettings.PostEditTimeLimit = -1
|
|
|
|
|
if err := a.SaveConfig(config, true); err != nil {
|
|
|
|
|
mlog.Error("Failed to update config in Advanced Permissions Phase 1 Migration.", mlog.String("error", err.Error()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-06 15:34:08 +00:00
|
|
|
system := model.System{
|
|
|
|
|
Name: ADVANCED_PERMISSIONS_MIGRATION_KEY,
|
|
|
|
|
Value: "true",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if result := <-a.Srv.Store.System().Save(&system); result.Err != nil {
|
2018-04-27 12:49:45 -07:00
|
|
|
mlog.Critical("Failed to mark advanced permissions migration as completed.")
|
|
|
|
|
mlog.Critical(fmt.Sprint(result.Err))
|
2018-02-06 15:34:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
2018-05-21 06:10:26 -04:00
|
|
|
|
|
|
|
|
func (a *App) SetPhase2PermissionsMigrationStatus(isComplete bool) error {
|
|
|
|
|
if !isComplete {
|
|
|
|
|
res := <-a.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2)
|
|
|
|
|
if res.Err != nil {
|
|
|
|
|
return res.Err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
a.phase2PermissionsMigrationComplete = isComplete
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2018-05-29 16:58:12 +02:00
|
|
|
|
|
|
|
|
func (a *App) DoEmojisPermissionsMigration() {
|
|
|
|
|
// If the migration is already marked as completed, don't do it again.
|
|
|
|
|
if result := <-a.Srv.Store.System().GetByName(EMOJIS_PERMISSIONS_MIGRATION_KEY); result.Err == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var role *model.Role = nil
|
|
|
|
|
var systemAdminRole *model.Role = nil
|
|
|
|
|
var err *model.AppError = nil
|
|
|
|
|
|
|
|
|
|
mlog.Info("Migrating emojis config to database.")
|
|
|
|
|
switch *a.Config().ServiceSettings.RestrictCustomEmojiCreation {
|
|
|
|
|
case model.RESTRICT_EMOJI_CREATION_ALL:
|
|
|
|
|
role, err = a.GetRoleByName(model.SYSTEM_USER_ROLE_ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical(err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case model.RESTRICT_EMOJI_CREATION_ADMIN:
|
|
|
|
|
role, err = a.GetRoleByName(model.TEAM_ADMIN_ROLE_ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical(err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
case model.RESTRICT_EMOJI_CREATION_SYSTEM_ADMIN:
|
|
|
|
|
role = nil
|
|
|
|
|
break
|
|
|
|
|
default:
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical("Invalid restrict emoji creation setting")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if role != nil {
|
|
|
|
|
role.Permissions = append(role.Permissions, model.PERMISSION_MANAGE_EMOJIS.Id)
|
|
|
|
|
if result := <-a.Srv.Store.Role().Save(role); result.Err != nil {
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical(result.Err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
systemAdminRole, err = a.GetRoleByName(model.SYSTEM_ADMIN_ROLE_ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical(err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
systemAdminRole.Permissions = append(systemAdminRole.Permissions, model.PERMISSION_MANAGE_EMOJIS.Id)
|
|
|
|
|
systemAdminRole.Permissions = append(systemAdminRole.Permissions, model.PERMISSION_MANAGE_OTHERS_EMOJIS.Id)
|
|
|
|
|
if result := <-a.Srv.Store.Role().Save(systemAdminRole); result.Err != nil {
|
|
|
|
|
mlog.Critical("Failed to migrate emojis creation permissions from mattermost config.")
|
|
|
|
|
mlog.Critical(result.Err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
system := model.System{
|
|
|
|
|
Name: EMOJIS_PERMISSIONS_MIGRATION_KEY,
|
|
|
|
|
Value: "true",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if result := <-a.Srv.Store.System().Save(&system); result.Err != nil {
|
|
|
|
|
mlog.Critical("Failed to mark emojis permissions migration as completed.")
|
|
|
|
|
mlog.Critical(fmt.Sprint(result.Err))
|
|
|
|
|
}
|
|
|
|
|
}
|