Merge branch 'master' into post-metadata

Этот коммит содержится в:
Harrison Healey
2018-11-14 09:58:56 -05:00
родитель fab63f8ba2 e0569e766a
Коммит d07def5169
152 изменённых файлов: 4303 добавлений и 801 удалений

16
Gopkg.lock сгенерированный
Просмотреть файл

@@ -33,6 +33,14 @@
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:705c40022f5c03bf96ffeb6477858d88565064485a513abcd0f11a0911546cb6"
name = "github.com/blang/semver"
packages = ["."]
pruneopts = "UT"
revision = "2ee87856327ba09384cabd113bc6b5d174e9ec0f"
version = "v3.5.1"
[[projects]]
branch = "master"
digest = "1:cc439e1d9d8cff3d575642f5401033b00f2b8d0cd9f859db45604701c990879a"
@@ -633,12 +641,12 @@
revision = "204274ad699c0983a70203a566887f17a717fef4"
[[projects]]
digest = "1:dc2d85c13ac22c22a1f3170a41a8e1b897fa05134aaf533f16df44f66a25b4a1"
digest = "1:69b1cc331fca23d702bd72f860c6a647afd0aa9fcbc1d0659b1365e26546dd70"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "a67f783a3814b8729bd2dac5780b5f78f8dbd64d"
version = "v1.1.0"
revision = "bcd833dfe83d3cebad139e4a29ed79cb2318bf95"
version = "v1.2.0"
[[projects]]
digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd"
@@ -984,6 +992,7 @@
input-imports = [
"github.com/NYTimes/gziphandler",
"github.com/avct/uasurfer",
"github.com/blang/semver",
"github.com/dgryski/dgoogauth",
"github.com/disintegration/imaging",
"github.com/dyatlov/go-opengraph/opengraph",
@@ -1022,6 +1031,7 @@
"github.com/rs/cors",
"github.com/rwcarlsen/goexif/exif",
"github.com/segmentio/analytics-go",
"github.com/sirupsen/logrus",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/mock",

Просмотреть файл

@@ -54,6 +54,14 @@
name = "github.com/hashicorp/go-plugin"
revision = "a4620f9913d19f03a6bf19b2f304daaaf83ea130"
[[constraint]]
name = "github.com/blang/semver"
version = "~3.5.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.2.0"

Просмотреть файл

@@ -65,6 +65,6 @@ Receive notifications of critical security updates. The sophistication of online
- **Twitter** - Follow [Mattermost](https://twitter.com/mattermost)
- **Blog** - Get the latest updates from the [Mattermost blog](https://about.mattermost.com/blog/).
- **Email** - Subscribe to our [newsletter](http://mattermost.us11.list-manage.com/subscribe?u=6cdba22349ae374e188e7ab8e&id=2add1c8034) (1 or 2 per month)
- **IRC** - Join us on #matterbridge (thanks to [matterircd](https://github.com/42wim/matterircd))
- **IRC** - Join the #matterbridge channel on [Freenode](https://freenode.net/) (thanks to [matterircd](https://github.com/42wim/matterircd))
Any other questions, mail us at info@mattermost.com. Wed love to meet you!

Просмотреть файл

@@ -4,17 +4,18 @@
package api4
import (
"net/http"
"github.com/mattermost/mattermost-server/app"
"github.com/mattermost/mattermost-server/model"
"net/http"
)
func (api *API) InitTermsOfService() {
api.BaseRoutes.TermsOfService.Handle("", api.ApiSessionRequired(getTermsOfService)).Methods("GET")
api.BaseRoutes.TermsOfService.Handle("", api.ApiSessionRequired(getLatestTermsOfService)).Methods("GET")
api.BaseRoutes.TermsOfService.Handle("", api.ApiSessionRequired(createTermsOfService)).Methods("POST")
}
func getTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
func getLatestTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
termsOfService, err := c.App.GetLatestTermsOfService()
if err != nil {
c.Err = err

Просмотреть файл

@@ -1,9 +1,10 @@
package api4
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetTermsOfService(t *testing.T) {

Просмотреть файл

@@ -40,7 +40,8 @@ func (api *API) InitUser() {
api.BaseRoutes.Users.Handle("/password/reset/send", api.ApiHandler(sendPasswordReset)).Methods("POST")
api.BaseRoutes.Users.Handle("/email/verify", api.ApiHandler(verifyUserEmail)).Methods("POST")
api.BaseRoutes.Users.Handle("/email/verify/send", api.ApiHandler(sendVerificationEmail)).Methods("POST")
api.BaseRoutes.User.Handle("/terms_of_service", api.ApiSessionRequired(registerTermsOfServiceAction)).Methods("POST")
api.BaseRoutes.User.Handle("/terms_of_service", api.ApiSessionRequired(saveUserTermsOfService)).Methods("POST")
api.BaseRoutes.User.Handle("/terms_of_service", api.ApiSessionRequired(getUserTermsOfService)).Methods("GET")
api.BaseRoutes.User.Handle("/auth", api.ApiSessionRequiredTrustRequester(updateUserAuth)).Methods("PUT")
@@ -845,7 +846,7 @@ func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAuditWithUserId(user.Id, fmt.Sprintf("active=%v", active))
if isSelfDeactive {
c.App.Go(func() {
c.App.Srv.Go(func() {
if err = c.App.SendDeactivateAccountEmail(user.Email, user.Locale, c.App.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
@@ -1626,7 +1627,7 @@ func enableUserAccessToken(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
func registerTermsOfServiceAction(c *Context, w http.ResponseWriter, r *http.Request) {
func saveUserTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.StringInterfaceFromJson(r.Body)
userId := c.Session.UserId
@@ -1638,7 +1639,7 @@ func registerTermsOfServiceAction(c *Context, w http.ResponseWriter, r *http.Req
return
}
if err := c.App.RecordUserTermsOfServiceAction(userId, termsOfServiceId, accepted); err != nil {
if err := c.App.SaveUserTermsOfService(userId, termsOfServiceId, accepted); err != nil {
c.Err = err
return
}
@@ -1646,3 +1647,13 @@ func registerTermsOfServiceAction(c *Context, w http.ResponseWriter, r *http.Req
c.LogAudit("TermsOfServiceId=" + termsOfServiceId + ", accepted=" + strconv.FormatBool(accepted))
ReturnStatusOK(w)
}
func getUserTermsOfService(c *Context, w http.ResponseWriter, r *http.Request) {
userId := c.Session.UserId
if result, err := c.App.GetUserTermsOfService(userId); err != nil {
c.Err = err
return
} else {
w.Write([]byte(result.ToJson()))
}
}

Просмотреть файл

@@ -3087,10 +3087,34 @@ func TestRegisterTermsOfServiceAction(t *testing.T) {
CheckNoError(t, resp)
assert.True(t, *success)
user, err := th.App.GetUser(th.BasicUser.Id)
_, err = th.App.GetUser(th.BasicUser.Id)
if err != nil {
t.Fatal(err)
}
}
func TestGetUserTermsOfService(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
Client := th.Client
_, resp := Client.GetUserTermsOfService(th.BasicUser.Id, "")
CheckErrorMessage(t, resp, "store.sql_user_terms_of_service.get_by_user.no_rows.app_error")
termsOfService, err := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, user.AcceptedTermsOfServiceId, termsOfService.Id)
success, resp := Client.RegisteTermsOfServiceAction(th.BasicUser.Id, termsOfService.Id, true)
CheckNoError(t, resp)
assert.True(t, *success)
userTermsOfService, resp := Client.GetUserTermsOfService(th.BasicUser.Id, "")
CheckNoError(t, resp)
assert.Equal(t, th.BasicUser.Id, userTermsOfService.UserId)
assert.Equal(t, termsOfService.Id, userTermsOfService.TermsOfServiceId)
assert.NotEmpty(t, userTermsOfService.CreateAt)
}

Просмотреть файл

@@ -139,7 +139,7 @@ func (a *App) InvalidateAllCaches() *model.AppError {
func (a *App) InvalidateAllCachesSkipSend() {
mlog.Info("Purging all caches")
a.sessionCache.Purge()
a.Srv.sessionCache.Purge()
ClearStatusCache()
a.Srv.Store.Channel().ClearCaches()
a.Srv.Store.User().ClearCaches()
@@ -212,8 +212,8 @@ func (a *App) RecycleDatabaseConnection() {
oldStore := a.Srv.Store
mlog.Warn("Attempting to recycle the database connection.")
a.Srv.Store = a.newStore()
a.Jobs.Store = a.Srv.Store
a.Srv.Store = a.Srv.newStore()
a.Srv.Jobs.Store = a.Srv.Store
if a.Srv.Store != oldStore {
time.Sleep(20 * time.Second)

Просмотреть файл

@@ -4,19 +4,15 @@
package app
import (
"crypto/ecdsa"
"fmt"
"html/template"
"net/http"
"path"
"reflect"
"strconv"
"sync"
"sync/atomic"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/throttled/throttled"
"github.com/mattermost/mattermost-server/einterfaces"
ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs"
@@ -24,7 +20,6 @@ import (
tjobs "github.com/mattermost/mattermost-server/jobs/interfaces"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/services/httpservice"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/store/sqlstore"
@@ -35,26 +30,10 @@ const ADVANCED_PERMISSIONS_MIGRATION_KEY = "AdvancedPermissionsMigrationComplete
const EMOJIS_PERMISSIONS_MIGRATION_KEY = "EmojisPermissionsMigrationComplete"
type App struct {
goroutineCount int32
goroutineExitSignal chan struct{}
Srv *Server
Log *mlog.Logger
Plugins *plugin.Environment
PluginConfigListenerId string
EmailBatching *EmailBatchingJob
EmailRateLimiter *throttled.GCRARateLimiter
Hubs []*Hub
HubsStopCheckingForDeadlock chan bool
PushNotificationsHub PushNotificationsHub
Jobs *jobs.JobServer
AccountMigration einterfaces.AccountMigrationInterface
Cluster einterfaces.ClusterInterface
Compliance einterfaces.ComplianceInterface
@@ -66,42 +45,6 @@ type App struct {
Mfa einterfaces.MfaInterface
Saml einterfaces.SamlInterface
config atomic.Value
envConfig map[string]interface{}
configFile string
configListeners map[string]func(*model.Config, *model.Config)
clusterLeaderListeners sync.Map
licenseValue atomic.Value
clientLicenseValue atomic.Value
licenseListeners map[string]func()
timezones atomic.Value
siteURL string
newStore func() store.Store
htmlTemplateWatcher *utils.HTMLTemplateWatcher
sessionCache *utils.Cache
configListenerId string
licenseListenerId string
logListenerId string
clusterLeaderListenerId string
disableConfigWatch bool
configWatcher *utils.ConfigWatcher
asymmetricSigningKey *ecdsa.PrivateKey
pluginCommands []*PluginCommand
pluginCommandsLock sync.RWMutex
clientConfig map[string]string
clientConfigHash string
limitedClientConfig map[string]string
diagnosticId string
phase2PermissionsMigrationComplete bool
HTTPService httpservice.HTTPService
}
@@ -118,15 +61,15 @@ func New(options ...Option) (outApp *App, outErr error) {
rootRouter := mux.NewRouter()
app := &App{
goroutineExitSignal: make(chan struct{}, 1),
Srv: &Server{
RootRouter: rootRouter,
goroutineExitSignal: make(chan struct{}, 1),
RootRouter: rootRouter,
configFile: "config.json",
configListeners: make(map[string]func(*model.Config, *model.Config)),
licenseListeners: map[string]func(){},
sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE),
clientConfig: make(map[string]string),
},
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(){},
}
app.HTTPService = httpservice.MakeHTTPService(app)
@@ -151,7 +94,7 @@ func New(options ...Option) (outApp *App, outErr error) {
}
model.AppErrorInit(utils.T)
if err := app.LoadConfig(app.configFile); err != nil {
if err := app.LoadConfig(app.Srv.configFile); err != nil {
return nil, err
}
@@ -164,7 +107,7 @@ func New(options ...Option) (outApp *App, outErr error) {
// 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.Srv.logListenerId = app.AddConfigListener(func(_, after *model.Config) {
app.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings))
})
@@ -176,22 +119,22 @@ func New(options ...Option) (outApp *App, outErr error) {
return nil, errors.Wrapf(err, "unable to load Mattermost translation files")
}
app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
app.Srv.configListenerId = app.AddConfigListener(func(_, _ *model.Config) {
app.configOrLicenseListener()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil)
message.Add("config", app.ClientConfigWithComputed())
app.Go(func() {
app.Srv.Go(func() {
app.Publish(message)
})
})
app.licenseListenerId = app.AddLicenseListener(func() {
app.Srv.licenseListenerId = app.AddLicenseListener(func() {
app.configOrLicenseListener()
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil)
message.Add("license", app.GetSanitizedClientLicense())
app.Go(func() {
app.Srv.Go(func() {
app.Publish(message)
})
@@ -205,8 +148,8 @@ func New(options ...Option) (outApp *App, outErr error) {
app.initEnterprise()
if app.newStore == nil {
app.newStore = func() store.Store {
if app.Srv.newStore == nil {
app.Srv.newStore = func() store.Store {
return store.NewLayeredStore(sqlstore.NewSqlSupplier(app.Config().SqlSettings, app.Metrics), app.Metrics, app.Cluster)
}
}
@@ -214,10 +157,10 @@ func New(options ...Option) (outApp *App, outErr error) {
if htmlTemplateWatcher, err := utils.NewHTMLTemplateWatcher("templates"); err != nil {
mlog.Error(fmt.Sprintf("Failed to parse server templates %v", err))
} else {
app.htmlTemplateWatcher = htmlTemplateWatcher
app.Srv.htmlTemplateWatcher = htmlTemplateWatcher
}
app.Srv.Store = app.newStore()
app.Srv.Store = app.Srv.newStore()
if err := app.ensureAsymmetricSigningKey(); err != nil {
return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key")
@@ -235,9 +178,9 @@ func New(options ...Option) (outApp *App, outErr error) {
app.initJobs()
})
app.clusterLeaderListenerId = app.AddClusterLeaderChangedListener(func() {
app.Srv.clusterLeaderListenerId = app.AddClusterLeaderChangedListener(func() {
mlog.Info("Cluster leader changed. Determining if job schedulers should be running:", mlog.Bool("isLeader", app.IsLeader()))
app.Jobs.Schedulers.HandleClusterLeaderChange(app.IsLeader())
app.Srv.Jobs.Schedulers.HandleClusterLeaderChange(app.IsLeader())
})
subpath, err := utils.GetSubpathFromConfig(app.Config())
@@ -281,26 +224,26 @@ func (a *App) Shutdown() {
a.StopPushNotificationsHubWorkers()
a.ShutDownPlugins()
a.WaitForGoroutines()
a.Srv.WaitForGoroutines()
if a.Srv.Store != nil {
a.Srv.Store.Close()
}
a.Srv = nil
if a.htmlTemplateWatcher != nil {
a.htmlTemplateWatcher.Close()
if a.Srv.htmlTemplateWatcher != nil {
a.Srv.htmlTemplateWatcher.Close()
}
a.RemoveConfigListener(a.configListenerId)
a.RemoveLicenseListener(a.licenseListenerId)
a.RemoveConfigListener(a.logListenerId)
a.RemoveClusterLeaderChangedListener(a.clusterLeaderListenerId)
a.RemoveConfigListener(a.Srv.configListenerId)
a.RemoveLicenseListener(a.Srv.licenseListenerId)
a.RemoveConfigListener(a.Srv.logListenerId)
a.RemoveClusterLeaderChangedListener(a.Srv.clusterLeaderListenerId)
mlog.Info("Server stopped")
a.DisableConfigWatch()
a.HTTPService.Close()
a.Srv = nil
}
var accountMigrationInterface func(*App) einterfaces.AccountMigrationInterface
@@ -441,39 +384,39 @@ func (a *App) initEnterprise() {
}
func (a *App) initJobs() {
a.Jobs = jobs.NewJobServer(a, a.Srv.Store)
a.Srv.Jobs = jobs.NewJobServer(a, a.Srv.Store)
if jobsDataRetentionJobInterface != nil {
a.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(a)
a.Srv.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(a)
}
if jobsMessageExportJobInterface != nil {
a.Jobs.MessageExportJob = jobsMessageExportJobInterface(a)
a.Srv.Jobs.MessageExportJob = jobsMessageExportJobInterface(a)
}
if jobsElasticsearchAggregatorInterface != nil {
a.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(a)
a.Srv.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(a)
}
if jobsElasticsearchIndexerInterface != nil {
a.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(a)
a.Srv.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(a)
}
if jobsLdapSyncInterface != nil {
a.Jobs.LdapSync = jobsLdapSyncInterface(a)
a.Srv.Jobs.LdapSync = jobsLdapSyncInterface(a)
}
if jobsMigrationsInterface != nil {
a.Jobs.Migrations = jobsMigrationsInterface(a)
a.Srv.Jobs.Migrations = jobsMigrationsInterface(a)
}
a.Jobs.Workers = a.Jobs.InitWorkers()
a.Jobs.Schedulers = a.Jobs.InitSchedulers()
a.Srv.Jobs.Workers = a.Srv.Jobs.InitWorkers()
a.Srv.Jobs.Schedulers = a.Srv.Jobs.InitSchedulers()
}
func (a *App) DiagnosticId() string {
return a.diagnosticId
return a.Srv.diagnosticId
}
func (a *App) SetDiagnosticId(id string) {
a.diagnosticId = id
a.Srv.diagnosticId = id
}
func (a *App) EnsureDiagnosticId() {
if a.diagnosticId != "" {
if a.Srv.diagnosticId != "" {
return
}
if result := <-a.Srv.Store.System().Get(); result.Err == nil {
@@ -486,36 +429,13 @@ func (a *App) EnsureDiagnosticId() {
<-a.Srv.Store.System().Save(systemId)
}
a.diagnosticId = id
}
}
// 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
a.Srv.diagnosticId = id
}
}
func (a *App) HTMLTemplates() *template.Template {
if a.htmlTemplateWatcher != nil {
return a.htmlTemplateWatcher.Templates()
if a.Srv.htmlTemplateWatcher != nil {
return a.Srv.htmlTemplateWatcher.Templates()
}
return nil
@@ -598,7 +518,7 @@ func (a *App) SetPhase2PermissionsMigrationStatus(isComplete bool) error {
return res.Err
}
}
a.phase2PermissionsMigrationComplete = isComplete
a.Srv.phase2PermissionsMigrationComplete = isComplete
return nil
}
@@ -672,7 +592,7 @@ func (a *App) DoEmojisPermissionsMigration() {
}
func (a *App) StartElasticsearch() {
a.Go(func() {
a.Srv.Go(func() {
if err := a.Elasticsearch.Start(); err != nil {
mlog.Error(err.Error())
}
@@ -680,19 +600,19 @@ func (a *App) StartElasticsearch() {
a.AddConfigListener(func(oldConfig *model.Config, newConfig *model.Config) {
if !*oldConfig.ElasticsearchSettings.EnableIndexing && *newConfig.ElasticsearchSettings.EnableIndexing {
a.Go(func() {
a.Srv.Go(func() {
if err := a.Elasticsearch.Start(); err != nil {
mlog.Error(err.Error())
}
})
} else if *oldConfig.ElasticsearchSettings.EnableIndexing && !*newConfig.ElasticsearchSettings.EnableIndexing {
a.Go(func() {
a.Srv.Go(func() {
if err := a.Elasticsearch.Stop(); err != nil {
mlog.Error(err.Error())
}
})
} else if *oldConfig.ElasticsearchSettings.Password != *newConfig.ElasticsearchSettings.Password || *oldConfig.ElasticsearchSettings.Username != *newConfig.ElasticsearchSettings.Username || *oldConfig.ElasticsearchSettings.ConnectionUrl != *newConfig.ElasticsearchSettings.ConnectionUrl || *oldConfig.ElasticsearchSettings.Sniff != *newConfig.ElasticsearchSettings.Sniff {
a.Go(func() {
a.Srv.Go(func() {
if *oldConfig.ElasticsearchSettings.EnableIndexing {
if err := a.Elasticsearch.Stop(); err != nil {
mlog.Error(err.Error())
@@ -707,13 +627,13 @@ func (a *App) StartElasticsearch() {
a.AddLicenseListener(func() {
if a.License() != nil {
a.Go(func() {
a.Srv.Go(func() {
if err := a.Elasticsearch.Start(); err != nil {
mlog.Error(err.Error())
}
})
} else {
a.Go(func() {
a.Srv.Go(func() {
if err := a.Elasticsearch.Stop(); err != nil {
mlog.Error(err.Error())
}

Просмотреть файл

@@ -246,8 +246,12 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
restrictPrivateChannel := *th.App.Config().TeamSettings.DEPRECATED_DO_NOT_USE_RestrictPrivateChannelManagement
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.DEPRECATED_DO_NOT_USE_RestrictPublicChannelManagement = restrictPublicChannel })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.DEPRECATED_DO_NOT_USE_RestrictPrivateChannelManagement = restrictPrivateChannel })
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.DEPRECATED_DO_NOT_USE_RestrictPublicChannelManagement = restrictPublicChannel
})
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.TeamSettings.DEPRECATED_DO_NOT_USE_RestrictPrivateChannelManagement = restrictPrivateChannel
})
}()
th.App.UpdateConfig(func(cfg *model.Config) {
@@ -433,8 +437,8 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
postEditTimeLimit := *th.App.Config().ServiceSettings.PostEditTimeLimit
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.DEPRECATED_DO_NOT_USE_AllowEditPost = allowEditPost})
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = postEditTimeLimit})
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.DEPRECATED_DO_NOT_USE_AllowEditPost = allowEditPost })
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.PostEditTimeLimit = postEditTimeLimit })
}()
th.App.UpdateConfig(func(cfg *model.Config) {

Просмотреть файл

@@ -212,9 +212,9 @@ func (a *App) CreateChannel(channel *model.Channel, addMember bool) (*model.Chan
}
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.ChannelHasBeenCreated(pluginContext, sc)
return true
}, plugin.ChannelHasBeenCreatedId)
@@ -239,9 +239,9 @@ func (a *App) CreateDirectChannel(userId string, otherUserId string) (*model.Cha
a.InvalidateCacheForUser(otherUserId)
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.ChannelHasBeenCreated(pluginContext, channel)
return true
}, plugin.ChannelHasBeenCreatedId)
@@ -663,6 +663,10 @@ func (a *App) UpdateChannelMemberNotifyProps(data map[string]string, channelId s
member.NotifyProps[model.PUSH_NOTIFY_PROP] = push
}
if ignoreChannelMentions, exists := data[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; exists {
member.NotifyProps[model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP] = ignoreChannelMentions
}
result := <-a.Srv.Store.Channel().UpdateMember(member)
if result.Err != nil {
return nil, result.Err
@@ -854,9 +858,9 @@ func (a *App) AddChannelMember(userId string, channel *model.Channel, userReques
}
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasJoinedChannel(pluginContext, cm, userRequestor)
return true
}, plugin.UserHasJoinedChannelId)
@@ -866,7 +870,7 @@ func (a *App) AddChannelMember(userId string, channel *model.Channel, userReques
if userRequestorId == "" || userId == userRequestorId {
a.postJoinChannelMessage(user, channel)
} else {
a.Go(func() {
a.Srv.Go(func() {
a.PostAddToChannelMessage(userRequestor, user, channel, postRootId)
})
}
@@ -1244,9 +1248,9 @@ func (a *App) JoinChannel(channel *model.Channel, userId string) *model.AppError
}
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasJoinedChannel(pluginContext, cm, nil)
return true
}, plugin.UserHasJoinedChannelId)
@@ -1336,7 +1340,7 @@ func (a *App) LeaveChannel(channelId string, userId string) *model.AppError {
return nil
}
a.Go(func() {
a.Srv.Go(func() {
a.postLeaveChannelMessage(user, channel)
})
@@ -1451,9 +1455,9 @@ func (a *App) removeUserFromChannel(userIdToRemove string, removerUserId string,
actorUser, _ = a.GetUser(removerUserId)
}
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasLeftChannel(pluginContext, cm, actorUser)
return true
}, plugin.UserHasLeftChannelId)
@@ -1489,7 +1493,7 @@ func (a *App) RemoveUserFromChannel(userIdToRemove string, removerUserId string,
if userIdToRemove == removerUserId {
a.postLeaveChannelMessage(user, channel)
} else {
a.Go(func() {
a.Srv.Go(func() {
a.postRemoveFromChannelMessage(removerUserId, user, channel)
})
}

Просмотреть файл

@@ -13,19 +13,19 @@ import (
// be called.
func (a *App) AddClusterLeaderChangedListener(listener func()) string {
id := model.NewId()
a.clusterLeaderListeners.Store(id, listener)
a.Srv.clusterLeaderListeners.Store(id, listener)
return id
}
// Removes a listener function by the unique ID returned when AddConfigListener was called
func (a *App) RemoveClusterLeaderChangedListener(id string) {
a.clusterLeaderListeners.Delete(id)
a.Srv.clusterLeaderListeners.Delete(id)
}
func (a *App) InvokeClusterLeaderChangedListeners() {
mlog.Info("Cluster leader changed. Invoking ClusterLeaderChanged listeners.")
a.Go(func() {
a.clusterLeaderListeners.Range(func(_, listener interface{}) bool {
a.Srv.Go(func() {
a.Srv.clusterLeaderListeners.Range(func(_, listener interface{}) bool {
listener.(func())()
return true
})

Просмотреть файл

@@ -78,7 +78,7 @@ func (me *EchoProvider) DoCommand(a *App, args *model.CommandArgs, message strin
}
echoSem <- true
a.Go(func() {
a.Srv.Go(func() {
defer func() { <-echoSem }()
post := &model.Post{}
post.ChannelId = args.ChannelId

Просмотреть файл

@@ -51,5 +51,5 @@ func (me *LeaveProvider) DoCommand(a *App, args *model.CommandArgs, message stri
return &model.CommandResponse{Text: args.T("api.command_leave.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + model.DEFAULT_CHANNEL, Text: args.T("api.command_leave.success"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
return &model.CommandResponse{GotoLocation: args.SiteURL + "/" + team.Name + "/channels/" + model.DEFAULT_CHANNEL}
}

86
app/command_leave_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,86 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model"
)
func TestLeaveProviderDoCommand(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
lp := LeaveProvider{}
publicChannel, _ := th.App.CreateChannel(&model.Channel{
DisplayName: "AA",
Name: "aa" + model.NewId() + "a",
Type: model.CHANNEL_OPEN,
TeamId: th.BasicTeam.Id,
CreatorId: th.BasicUser.Id,
}, false)
privateChannel, _ := th.App.CreateChannel(&model.Channel{
DisplayName: "BB",
Name: "aa" + model.NewId() + "a",
Type: model.CHANNEL_OPEN,
TeamId: th.BasicTeam.Id,
CreatorId: th.BasicUser.Id,
}, false)
th.App.AddUserToTeam(th.BasicTeam.Id, th.BasicUser.Id, th.BasicUser.Id)
th.App.AddUserToChannel(th.BasicUser, publicChannel)
th.App.AddUserToChannel(th.BasicUser, privateChannel)
args := &model.CommandArgs{
T: func(s string, args ...interface{}) string { return s },
}
// Should error when no Channel ID in args
actual := lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
// Should error when no Team ID in args
args.ChannelId = publicChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.command_leave.fail.app_error", actual.Text)
assert.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, actual.ResponseType)
// Leave a public channel
siteURL := "http://localhost:8065"
args.TeamId = th.BasicTeam.Id
args.SiteURL = siteURL
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
assert.Equal(t, siteURL+"/"+th.BasicTeam.Name+"/channels/"+model.DEFAULT_CHANNEL, actual.GotoLocation)
assert.Equal(t, "", actual.ResponseType)
time.Sleep(100 * time.Millisecond)
member, err := th.App.GetChannelMember(publicChannel.Id, th.BasicUser.Id)
if member == nil {
t.Errorf("Expected member object, got nil")
}
if err != nil {
t.Errorf("Expected nil object, got %s", err)
}
// Leave a private channel
args.ChannelId = privateChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "", actual.Text)
// Should not leave a default channel
defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false)
args.ChannelId = defaultChannel.Id
actual = lp.DoCommand(th.App, args, "")
assert.Equal(t, "api.channel.leave.default.app_error", actual.Text)
}

Просмотреть файл

@@ -36,7 +36,7 @@ func (a *App) SaveComplianceReport(job *model.Compliance) (*model.Compliance, *m
}
job = result.Data.(*model.Compliance)
a.Go(func() {
a.Srv.Go(func() {
a.Compliance.RunComplianceJob(job)
})

Просмотреть файл

@@ -28,15 +28,15 @@ const (
)
func (a *App) Config() *model.Config {
if cfg := a.config.Load(); cfg != nil {
if cfg := a.Srv.config.Load(); cfg != nil {
return cfg.(*model.Config)
}
return &model.Config{}
}
func (a *App) EnvironmentConfig() map[string]interface{} {
if a.envConfig != nil {
return a.envConfig
if a.Srv.envConfig != nil {
return a.Srv.envConfig
}
return map[string]interface{}{}
}
@@ -45,7 +45,7 @@ func (a *App) UpdateConfig(f func(*model.Config)) {
old := a.Config()
updated := old.Clone()
f(updated)
a.config.Store(updated)
a.Srv.config.Store(updated)
a.InvokeConfigListeners(old, updated)
}
@@ -62,11 +62,10 @@ func (a *App) LoadConfig(configFile string) *model.AppError {
return err
}
*cfg.ServiceSettings.SiteURL = strings.TrimRight(*cfg.ServiceSettings.SiteURL, "/")
a.config.Store(cfg)
a.Srv.config.Store(cfg)
a.configFile = configPath
a.envConfig = envConfig
a.siteURL = *cfg.ServiceSettings.SiteURL
a.Srv.configFile = configPath
a.Srv.envConfig = envConfig
a.InvokeConfigListeners(old, cfg)
return nil
@@ -74,7 +73,7 @@ func (a *App) LoadConfig(configFile string) *model.AppError {
func (a *App) ReloadConfig() *model.AppError {
debug.FreeOSMemory()
if err := a.LoadConfig(a.configFile); err != nil {
if err := a.LoadConfig(a.Srv.configFile); err != nil {
return err
}
@@ -84,37 +83,37 @@ func (a *App) ReloadConfig() *model.AppError {
}
func (a *App) ConfigFileName() string {
return a.configFile
return a.Srv.configFile
}
func (a *App) ClientConfig() map[string]string {
return a.clientConfig
return a.Srv.clientConfig
}
func (a *App) ClientConfigHash() string {
return a.clientConfigHash
return a.Srv.clientConfigHash
}
func (a *App) LimitedClientConfig() map[string]string {
return a.limitedClientConfig
return a.Srv.limitedClientConfig
}
func (a *App) EnableConfigWatch() {
if a.configWatcher == nil && !a.disableConfigWatch {
if a.Srv.configWatcher == nil && !a.Srv.disableConfigWatch {
configWatcher, err := utils.NewConfigWatcher(a.ConfigFileName(), func() {
a.ReloadConfig()
})
if err != nil {
mlog.Error(fmt.Sprint(err))
}
a.configWatcher = configWatcher
a.Srv.configWatcher = configWatcher
}
}
func (a *App) DisableConfigWatch() {
if a.configWatcher != nil {
a.configWatcher.Close()
a.configWatcher = nil
if a.Srv.configWatcher != nil {
a.Srv.configWatcher.Close()
a.Srv.configWatcher = nil
}
}
@@ -123,17 +122,17 @@ func (a *App) DisableConfigWatch() {
// for the listener that can later be used to remove it.
func (a *App) AddConfigListener(listener func(*model.Config, *model.Config)) string {
id := model.NewId()
a.configListeners[id] = listener
a.Srv.configListeners[id] = listener
return id
}
// Removes a listener function by the unique ID returned when AddConfigListener was called
func (a *App) RemoveConfigListener(id string) {
delete(a.configListeners, id)
delete(a.Srv.configListeners, id)
}
func (a *App) InvokeConfigListeners(old, current *model.Config) {
for _, listener := range a.configListeners {
for _, listener := range a.Srv.configListeners {
listener(old, current)
}
}
@@ -141,7 +140,7 @@ func (a *App) InvokeConfigListeners(old, current *model.Config) {
// EnsureAsymmetricSigningKey ensures that an asymmetric signing key exists and future calls to
// AsymmetricSigningKey will always return a valid signing key.
func (a *App) ensureAsymmetricSigningKey() error {
if a.asymmetricSigningKey != nil {
if a.Srv.asymmetricSigningKey != nil {
return nil
}
@@ -202,7 +201,7 @@ func (a *App) ensureAsymmetricSigningKey() error {
default:
return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve)
}
a.asymmetricSigningKey = &ecdsa.PrivateKey{
a.Srv.asymmetricSigningKey = &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: key.ECDSAKey.X,
@@ -240,31 +239,31 @@ func (a *App) ensureInstallationDate() error {
// AsymmetricSigningKey will return a private key that can be used for asymmetric signing.
func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey {
return a.asymmetricSigningKey
return a.Srv.asymmetricSigningKey
}
func (a *App) regenerateClientConfig() {
a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
a.Srv.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License())
if a.clientConfig["EnableCustomTermsOfService"] == "true" {
if a.Srv.clientConfig["EnableCustomTermsOfService"] == "true" {
termsOfService, err := a.GetLatestTermsOfService()
if err != nil {
mlog.Err(err)
} else {
a.clientConfig["CustomTermsOfServiceId"] = termsOfService.Id
a.Srv.clientConfig["CustomTermsOfServiceId"] = termsOfService.Id
}
}
a.limitedClientConfig = utils.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License())
a.Srv.limitedClientConfig = utils.GenerateLimitedClientConfig(a.Config(), a.DiagnosticId(), a.License())
if key := a.AsymmetricSigningKey(); key != nil {
der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey)
a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
a.limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
a.Srv.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
a.Srv.limitedClientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der)
}
clientConfigJSON, _ := json.Marshal(a.clientConfig)
a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
clientConfigJSON, _ := json.Marshal(a.Srv.clientConfig)
a.Srv.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON))
}
func (a *App) Desanitize(cfg *model.Config) {
@@ -322,7 +321,7 @@ func (a *App) GetCookieDomain() string {
}
func (a *App) GetSiteURL() string {
return a.siteURL
return *a.Config().ServiceSettings.SiteURL
}
// ClientConfigWithComputed gets the configuration in a format suitable for sending to the client.

Просмотреть файл

@@ -35,11 +35,12 @@ func TestLoadConfig(t *testing.T) {
require.Nil(t, err)
tempConfig.Close()
a := App{}
a := App{
Srv: &Server{},
}
appErr := a.LoadConfig(tempConfig.Name())
require.Nil(t, appErr)
assert.Equal(t, "http://localhost:8065", a.siteURL)
assert.Equal(t, "http://localhost:8065", *a.GetConfig().ServiceSettings.SiteURL)
}

Просмотреть файл

@@ -408,13 +408,14 @@ func (a *App) trackConfig() {
})
a.SendDiagnostic(TRACK_CONFIG_SUPPORT, map[string]interface{}{
"isdefault_terms_of_service_link": isDefault(*cfg.SupportSettings.TermsOfServiceLink, model.SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK),
"isdefault_privacy_policy_link": isDefault(*cfg.SupportSettings.PrivacyPolicyLink, model.SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK),
"isdefault_about_link": isDefault(*cfg.SupportSettings.AboutLink, model.SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK),
"isdefault_help_link": isDefault(*cfg.SupportSettings.HelpLink, model.SUPPORT_SETTINGS_DEFAULT_HELP_LINK),
"isdefault_report_a_problem_link": isDefault(*cfg.SupportSettings.ReportAProblemLink, model.SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK),
"isdefault_support_email": isDefault(*cfg.SupportSettings.SupportEmail, model.SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL),
"custom_terms_of_service_enabled": *cfg.SupportSettings.CustomTermsOfServiceEnabled,
"isdefault_terms_of_service_link": isDefault(*cfg.SupportSettings.TermsOfServiceLink, model.SUPPORT_SETTINGS_DEFAULT_TERMS_OF_SERVICE_LINK),
"isdefault_privacy_policy_link": isDefault(*cfg.SupportSettings.PrivacyPolicyLink, model.SUPPORT_SETTINGS_DEFAULT_PRIVACY_POLICY_LINK),
"isdefault_about_link": isDefault(*cfg.SupportSettings.AboutLink, model.SUPPORT_SETTINGS_DEFAULT_ABOUT_LINK),
"isdefault_help_link": isDefault(*cfg.SupportSettings.HelpLink, model.SUPPORT_SETTINGS_DEFAULT_HELP_LINK),
"isdefault_report_a_problem_link": isDefault(*cfg.SupportSettings.ReportAProblemLink, model.SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK),
"isdefault_support_email": isDefault(*cfg.SupportSettings.SupportEmail, model.SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL),
"custom_terms_of_service_enabled": *cfg.SupportSettings.CustomTermsOfServiceEnabled,
"custom_terms_of_service_re_acceptance_period": *cfg.SupportSettings.CustomTermsOfServiceReAcceptancePeriod,
})
a.SendDiagnostic(TRACK_CONFIG_LDAP, map[string]interface{}{
@@ -590,7 +591,7 @@ func (a *App) trackPlugins() {
settingsCount := 0
pluginStates := a.Config().PluginSettings.PluginStates
plugins, _ := a.Plugins.Available()
plugins, _ := a.Srv.Plugins.Available()
if pluginStates != nil && plugins != nil {
for _, plugin := range plugins {

Просмотреть файл

@@ -42,7 +42,7 @@ func (a *App) SetupInviteEmailRateLimiting() error {
return errors.Wrap(err, "Unable to setup email rate limiting GCRA rate limiter.")
}
a.EmailRateLimiter = rateLimiter
a.Srv.EmailRateLimiter = rateLimiter
return nil
}
@@ -286,11 +286,11 @@ func (a *App) SendMfaChangeEmail(email string, activated bool, locale, siteURL s
}
func (a *App) SendInviteEmails(team *model.Team, senderName string, senderUserId string, invites []string, siteURL string) {
if a.EmailRateLimiter == nil {
if a.Srv.EmailRateLimiter == nil {
a.Log.Error("Email invite not sent, rate limiting could not be setup.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id))
return
}
rateLimited, result, err := a.EmailRateLimiter.RateLimit(senderUserId, len(invites))
rateLimited, result, err := a.Srv.EmailRateLimiter.RateLimit(senderUserId, len(invites))
if err != nil {
a.Log.Error("Error rate limiting invite email.", mlog.String("user_id", senderUserId), mlog.String("team_id", team.Id), mlog.Err(err))
return

Просмотреть файл

@@ -25,13 +25,13 @@ const (
func (a *App) InitEmailBatching() {
if *a.Config().EmailSettings.EnableEmailBatching {
if a.EmailBatching == nil {
a.EmailBatching = NewEmailBatchingJob(a, *a.Config().EmailSettings.EmailBatchingBufferSize)
if a.Srv.EmailBatching == nil {
a.Srv.EmailBatching = NewEmailBatchingJob(a, *a.Config().EmailSettings.EmailBatchingBufferSize)
}
// note that we don't support changing EmailBatchingBufferSize without restarting the server
a.EmailBatching.Start()
a.Srv.EmailBatching.Start()
}
}
@@ -40,7 +40,7 @@ func (a *App) AddNotificationEmailToBatch(user *model.User, post *model.Post, te
return model.NewAppError("AddNotificationEmailToBatch", "api.email_batching.add_notification_email_to_batch.disabled.app_error", nil, "", http.StatusNotImplemented)
}
if !a.EmailBatching.Add(user, post, team) {
if !a.Srv.EmailBatching.Add(user, post, team) {
mlog.Error("Email batching job's receiving channel was full. Please increase the EmailBatchingBufferSize.")
return model.NewAppError("AddNotificationEmailToBatch", "api.email_batching.add_notification_email_to_batch.channel_full.app_error", nil, "", http.StatusInternalServerError)
}
@@ -188,7 +188,7 @@ func (job *EmailBatchingJob) checkPendingNotifications(now time.Time, handler fu
// send the email notification if it's been long enough
if now.Sub(time.Unix(batchStartTime/1000, 0)) > time.Duration(interval)*time.Second {
job.app.Go(func(userId string, notifications []*batchedNotification) func() {
job.app.Srv.Go(func(userId string, notifications []*batchedNotification) func() {
return func() {
handler(userId, notifications)
}

Просмотреть файл

@@ -206,17 +206,21 @@ func (a *App) buildUserChannelMemberships(userId string, teamId string) (*[]User
var memberships []UserChannelImportData
result := <-a.Srv.Store.Channel().GetChannelMembersForExport(userId, teamId)
if result.Err != nil {
return nil, result.Err
}
members := result.Data.([]*model.ChannelMemberForExport)
for _, member := range members {
memberships = append(memberships, *ImportUserChannelDataFromChannelMember(member))
category := model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL
preferences, err := a.GetPreferenceByCategoryForUser(userId, category)
if err != nil {
return nil, err
}
for _, member := range members {
memberships = append(memberships, *ImportUserChannelDataFromChannelMemberAndPreferences(member, &preferences))
}
return &memberships, nil
}

Просмотреть файл

@@ -71,7 +71,7 @@ func ImportUserTeamDataFromTeamMember(member *model.TeamMemberForExport) *UserTe
}
}
func ImportUserChannelDataFromChannelMember(member *model.ChannelMemberForExport) *UserChannelImportData {
func ImportUserChannelDataFromChannelMemberAndPreferences(member *model.ChannelMemberForExport, preferences *model.Preferences) *UserChannelImportData {
rolesList := strings.Fields(member.Roles)
if member.SchemeAdmin {
rolesList = append(rolesList, model.CHANNEL_ADMIN_ROLE_ID)
@@ -95,11 +95,19 @@ func ImportUserChannelDataFromChannelMember(member *model.ChannelMemberForExport
notifyProps.MarkUnread = &markUnread
}
favorite := false
for _, preference := range *preferences {
if member.ChannelId == preference.Name {
favorite = true
}
}
roles := strings.Join(rolesList, " ")
return &UserChannelImportData{
Name: &member.ChannelName,
Roles: &roles,
NotifyProps: &notifyProps,
Favorite: &favorite,
}
}

Просмотреть файл

@@ -3,8 +3,9 @@ package app
import (
"testing"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
@@ -60,7 +61,7 @@ func TestExportUserNotifyProps(t *testing.T) {
require.Equal(t, userNotifyProps[model.MENTION_KEYS_NOTIFY_PROP], *exportNotifyProps.MentionKeys)
}
func TestExportUserChannelsNotifyProps(t *testing.T) {
func TestExportUserChannels(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
channel := th.BasicChannel
@@ -71,22 +72,34 @@ func TestExportUserChannelsNotifyProps(t *testing.T) {
model.DESKTOP_NOTIFY_PROP: model.USER_NOTIFY_ALL,
model.PUSH_NOTIFY_PROP: model.USER_NOTIFY_NONE,
}
preference := model.Preference{
UserId: user.Id,
Category: model.PREFERENCE_CATEGORY_FAVORITE_CHANNEL,
Name: channel.Id,
Value: "true",
}
var preferences model.Preferences
preferences = append(preferences, preference)
channelMember := model.ChannelMember{
ChannelId: channel.Id,
UserId: user.Id,
}
th.App.Srv.Store.Channel().SaveMember(&channelMember)
th.App.Srv.Store.Preference().Save(&preferences)
th.App.UpdateChannelMemberNotifyProps(notifyProps, channel.Id, user.Id)
exportData, _ := th.App.buildUserChannelMemberships(user.Id, team.Id)
assert.Equal(t, len(*exportData), 3)
for _, data := range *exportData {
if *data.Name == channelName {
assert.Equal(t, *data.NotifyProps.Desktop, "all")
assert.Equal(t, *data.NotifyProps.Mobile, "none")
assert.Equal(t, *data.NotifyProps.MarkUnread, "all") // default value
assert.True(t, *data.Favorite)
} else { // default values
assert.Equal(t, *data.NotifyProps.Desktop, "default")
assert.Equal(t, *data.NotifyProps.Mobile, "default")
assert.Equal(t, *data.NotifyProps.MarkUnread, "all")
assert.False(t, *data.Favorite)
}
}
}

Просмотреть файл

@@ -4,9 +4,10 @@
package app
import (
"github.com/mattermost/mattermost-server/model"
"html/template"
"net/http"
"github.com/mattermost/mattermost-server/model"
)
func (a *App) isExtensionSupportEnabled() bool {

Просмотреть файл

@@ -397,6 +397,25 @@ func (a *App) UploadFiles(teamId string, channelId string, userId string, files
return resStruct, nil
}
// UploadFile uploads a single file in form of a completely constructed byte array for a channel.
func (a *App) UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) {
info, _, appError := a.DoUploadFileExpectModification(time.Now(), "noteam", channelId, "nouser", filename, data)
if appError != nil {
return nil, appError
}
if info.PreviewPath != "" || info.ThumbnailPath != "" {
previewPathList := []string{info.PreviewPath}
thumbnailPathList := []string{info.ThumbnailPath}
imageDataList := [][]byte{data}
a.HandleImages(previewPathList, thumbnailPathList, imageDataList)
}
return info, nil
}
func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) {
info, _, err := a.DoUploadFileExpectModification(now, rawTeamId, rawChannelId, rawUserId, rawFilename, data)
return info, err
@@ -444,7 +463,7 @@ func (a *App) DoUploadFileExpectModification(now time.Time, rawTeamId string, ra
if a.PluginsReady() {
var rejectionError *model.AppError
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
var newBytes bytes.Buffer
replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes)
if rejectionReason != "" {

Просмотреть файл

@@ -107,6 +107,30 @@ func TestDoUploadFile(t *testing.T) {
}
}
func TestUploadFile(t *testing.T) {
th := Setup()
defer th.TearDown()
channelId := model.NewId()
filename := "test"
data := []byte("abcd")
info1, err := th.App.UploadFile(data, channelId, filename)
if err != nil {
t.Fatal(err)
} else {
defer func() {
<-th.App.Srv.Store.FileInfo().PermanentDelete(info1.Id)
th.App.RemoveFile(info1.Path)
}()
}
if info1.Path != fmt.Sprintf("%v/teams/noteam/channels/%v/users/nouser/%v/%v",
time.Now().Format("20060102"), channelId, info1.Id, filename) {
t.Fatal("stored file at incorrect path", info1.Path)
}
}
func TestGetInfoForFilename(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()

Просмотреть файл

@@ -40,9 +40,9 @@ func (a *App) GetJobsByType(jobType string, offset int, limit int) ([]*model.Job
}
func (a *App) CreateJob(job *model.Job) (*model.Job, *model.AppError) {
return a.Jobs.CreateJob(job.Type, job.Data)
return a.Srv.Jobs.CreateJob(job.Type, job.Data)
}
func (a *App) CancelJob(jobId string) *model.AppError {
return a.Jobs.RequestCancellation(jobId)
return a.Srv.Jobs.RequestCancellation(jobId)
}

Просмотреть файл

@@ -13,7 +13,7 @@ import (
)
func (a *App) SyncLdap() {
a.Go(func() {
a.Srv.Go(func() {
if license := a.License(); license != nil && *license.Features.LDAP && *a.Config().LdapSettings.EnableSync {
if ldapI := a.Ldap; ldapI != nil {
@@ -67,7 +67,7 @@ func (a *App) SwitchEmailToLdap(email, password, code, ldapLoginId, ldapPassword
return "", err
}
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
@@ -113,7 +113,7 @@ func (a *App) SwitchLdapToEmail(ldapPassword, code, email, newPassword string) (
T := utils.GetUserTranslations(user.Locale)
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}

Просмотреть файл

@@ -93,10 +93,10 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError)
// doesn't start until the server is restarted, which prevents the 'run job now' buttons in system console from
// functioning as expected
if *a.Config().JobSettings.RunJobs {
a.Jobs.StartWorkers()
a.Srv.Jobs.StartWorkers()
}
if *a.Config().JobSettings.RunScheduler {
a.Jobs.StartSchedulers()
a.Srv.Jobs.StartSchedulers()
}
return license, nil
@@ -104,13 +104,13 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError)
// License returns the currently active license or nil if the application is unlicensed.
func (a *App) License() *model.License {
license, _ := a.licenseValue.Load().(*model.License)
license, _ := a.Srv.licenseValue.Load().(*model.License)
return license
}
func (a *App) SetLicense(license *model.License) bool {
defer func() {
for _, listener := range a.licenseListeners {
for _, listener := range a.Srv.licenseListeners {
listener()
}
}()
@@ -119,14 +119,14 @@ func (a *App) SetLicense(license *model.License) bool {
license.Features.SetDefaults()
if !license.IsExpired() {
a.licenseValue.Store(license)
a.clientLicenseValue.Store(utils.GetClientLicense(license))
a.Srv.licenseValue.Store(license)
a.Srv.clientLicenseValue.Store(utils.GetClientLicense(license))
return true
}
}
a.licenseValue.Store((*model.License)(nil))
a.clientLicenseValue.Store(map[string]string(nil))
a.Srv.licenseValue.Store((*model.License)(nil))
a.Srv.clientLicenseValue.Store(map[string]string(nil))
return false
}
@@ -141,18 +141,18 @@ func (a *App) ValidateAndSetLicenseBytes(b []byte) {
}
func (a *App) SetClientLicense(m map[string]string) {
a.clientLicenseValue.Store(m)
a.Srv.clientLicenseValue.Store(m)
}
func (a *App) ClientLicense() map[string]string {
if clientLicense, _ := a.clientLicenseValue.Load().(map[string]string); clientLicense != nil {
if clientLicense, _ := a.Srv.clientLicenseValue.Load().(map[string]string); clientLicense != nil {
return clientLicense
}
return map[string]string{"IsLicensed": "false"}
}
func (a *App) RemoveLicense() *model.AppError {
if license, _ := a.licenseValue.Load().(*model.License); license == nil {
if license, _ := a.Srv.licenseValue.Load().(*model.License); license == nil {
return nil
}
@@ -174,12 +174,12 @@ func (a *App) RemoveLicense() *model.AppError {
func (a *App) AddLicenseListener(listener func()) string {
id := model.NewId()
a.licenseListeners[id] = listener
a.Srv.licenseListeners[id] = listener
return id
}
func (a *App) RemoveLicenseListener(id string) {
delete(a.licenseListeners, id)
delete(a.Srv.licenseListeners, id)
}
func (a *App) GetClientLicenseEtag(useSanitized bool) string {

Просмотреть файл

@@ -69,7 +69,7 @@ func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken string, l
if a.PluginsReady() {
var rejectionReason string
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
rejectionReason = hooks.UserWillLogIn(pluginContext, user)
return rejectionReason == ""
}, plugin.UserWillLogInId)
@@ -78,9 +78,9 @@ func (a *App) AuthenticateUserForLogin(id, loginId, password, mfaToken string, l
return nil, model.NewAppError("AuthenticateUserForLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
}
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasLoggedIn(pluginContext, user)
return true
}, plugin.UserHasLoggedInId)

Просмотреть файл

@@ -78,13 +78,13 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
}
if post.Type != model.POST_AUTO_RESPONDER {
a.Go(func() {
a.Srv.Go(func() {
a.SendAutoResponse(channel, otherUser)
})
}
} else {
keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE)
keywords := a.GetMentionKeywordsInChannel(profileMap, post.Type != model.POST_HEADER_CHANGE && post.Type != model.POST_PURPOSE_CHANGE, channelMemberNotifyPropsMap)
m := GetExplicitMentions(post, keywords)
@@ -127,7 +127,7 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
if result := <-a.Srv.Store.User().GetProfilesByUsernames(m.OtherPotentialMentions, team.Id); result.Err == nil {
outOfChannelMentions := result.Data.([]*model.User)
if channel.Type != model.CHANNEL_GROUP {
a.Go(func() {
a.Srv.Go(func() {
a.sendOutOfChannelMentions(sender, post, outOfChannelMentions)
})
}
@@ -560,7 +560,7 @@ func GetMentionsEnabledFields(post *model.Post) model.StringArray {
// Given a map of user IDs to profiles, returns a list of mention
// keywords for all users in the channel.
func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookForSpecialMentions bool) map[string][]string {
func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookForSpecialMentions bool, channelMemberNotifyPropsMap map[string]model.StringMap) map[string][]string {
keywords := make(map[string][]string)
for id, profile := range profiles {
@@ -582,9 +582,16 @@ func (a *App) GetMentionKeywordsInChannel(profiles map[string]*model.User, lookF
keywords[profile.FirstName] = append(keywords[profile.FirstName], profile.Id)
}
ignoreChannelMentions := false
if ignoreChannelMentionsNotifyProp, ok := channelMemberNotifyPropsMap[profile.Id][model.IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok {
if ignoreChannelMentionsNotifyProp == model.IGNORE_CHANNEL_MENTIONS_ON {
ignoreChannelMentions = true
}
}
// Add @channel and @all to keywords if user has them turned on
if lookForSpecialMentions {
if int64(len(profiles)) <= *a.Config().TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps["channel"] == "true" {
if int64(len(profiles)) <= *a.Config().TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps["channel"] == "true" && !ignoreChannelMentions {
keywords["@channel"] = append(keywords["@channel"], profile.Id)
keywords["@all"] = append(keywords["@all"], profile.Id)

Просмотреть файл

@@ -103,7 +103,7 @@ func (a *App) sendNotificationEmail(notification *postNotification, user *model.
teamURL := a.GetSiteURL() + "/" + team.Name
var bodyText = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, teamURL, emailNotificationContentsType, useMilitaryTime, translateFunc)
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendMail(user.Email, html.UnescapeString(subjectText), bodyText); err != nil {
mlog.Error(fmt.Sprint("Error to send the email", user.Email, err))
}

Просмотреть файл

@@ -132,7 +132,7 @@ func (a *App) sendPushNotification(notification *postNotification, user *model.U
channelName := notification.GetChannelName(nameFormat, user.Id)
senderName := notification.GetSenderName(nameFormat, cfg.ServiceSettings.EnablePostUsernameOverride)
c := a.PushNotificationsHub.GetGoChannelFromUserId(user.Id)
c := a.Srv.PushNotificationsHub.GetGoChannelFromUserId(user.Id)
c <- PushNotification{
notificationType: NOTIFICATION_TYPE_MESSAGE,
post: post,
@@ -217,7 +217,7 @@ func (a *App) ClearPushNotificationSync(userId string, channelId string) {
}
func (a *App) ClearPushNotification(userId string, channelId string) {
channel := a.PushNotificationsHub.GetGoChannelFromUserId(userId)
channel := a.Srv.PushNotificationsHub.GetGoChannelFromUserId(userId)
channel <- PushNotification{
notificationType: NOTIFICATION_TYPE_CLEAR,
userId: userId,
@@ -232,7 +232,7 @@ func (a *App) CreatePushNotificationsHub() {
for x := 0; x < PUSH_NOTIFICATION_HUB_WORKERS; x++ {
hub.Channels = append(hub.Channels, make(chan PushNotification, PUSH_NOTIFICATIONS_HUB_BUFFER_PER_WORKER))
}
a.PushNotificationsHub = hub
a.Srv.PushNotificationsHub = hub
}
func (a *App) pushNotificationWorker(notifications chan PushNotification) {
@@ -259,13 +259,13 @@ func (a *App) pushNotificationWorker(notifications chan PushNotification) {
func (a *App) StartPushNotificationsHubWorkers() {
for x := 0; x < PUSH_NOTIFICATION_HUB_WORKERS; x++ {
channel := a.PushNotificationsHub.Channels[x]
a.Go(func() { a.pushNotificationWorker(channel) })
channel := a.Srv.PushNotificationsHub.Channels[x]
a.Srv.Go(func() { a.pushNotificationWorker(channel) })
}
}
func (a *App) StopPushNotificationsHubWorkers() {
for _, channel := range a.PushNotificationsHub.Channels {
for _, channel := range a.Srv.PushNotificationsHub.Channels {
close(channel)
}
}

Просмотреть файл

@@ -625,8 +625,14 @@ func TestGetMentionKeywords(t *testing.T) {
},
}
channelMemberNotifyPropsMap1Off := map[string]model.StringMap{
user1.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
}
profiles := map[string]*model.User{user1.Id: user1}
mentions := th.App.GetMentionKeywordsInChannel(profiles, true)
mentions := th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap1Off)
if len(mentions) != 3 {
t.Fatal("should've returned three mention keywords")
} else if ids, ok := mentions["user"]; !ok || ids[0] != user1.Id {
@@ -647,8 +653,14 @@ func TestGetMentionKeywords(t *testing.T) {
},
}
channelMemberNotifyPropsMap2Off := map[string]model.StringMap{
user2.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
}
profiles = map[string]*model.User{user2.Id: user2}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true)
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap2Off)
if len(mentions) != 2 {
t.Fatal("should've returned two mention keyword")
} else if ids, ok := mentions["First"]; !ok || ids[0] != user2.Id {
@@ -665,8 +677,14 @@ func TestGetMentionKeywords(t *testing.T) {
},
}
// Channel-wide mentions are not ignored on channel level
channelMemberNotifyPropsMap3Off := map[string]model.StringMap{
user3.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
}
profiles = map[string]*model.User{user3.Id: user3}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true)
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3Off)
if len(mentions) != 3 {
t.Fatal("should've returned three mention keywords")
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
@@ -675,6 +693,45 @@ func TestGetMentionKeywords(t *testing.T) {
t.Fatal("should've returned mention key of @all")
}
// Channel member notify props is set to default
channelMemberNotifyPropsMapDefault := map[string]model.StringMap{
user3.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_DEFAULT,
},
}
profiles = map[string]*model.User{user3.Id: user3}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapDefault)
if len(mentions) != 3 {
t.Fatal("should've returned three mention keywords")
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
t.Fatal("should've returned mention key of @channel")
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id {
t.Fatal("should've returned mention key of @all")
}
// Channel member notify props is empty
channelMemberNotifyPropsMapEmpty := map[string]model.StringMap{}
profiles = map[string]*model.User{user3.Id: user3}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmpty)
if len(mentions) != 3 {
t.Fatal("should've returned three mention keywords")
} else if ids, ok := mentions["@channel"]; !ok || ids[0] != user3.Id {
t.Fatal("should've returned mention key of @channel")
} else if ids, ok := mentions["@all"]; !ok || ids[0] != user3.Id {
t.Fatal("should've returned mention key of @all")
}
// Channel-wide mentions are ignored channel level
channelMemberNotifyPropsMap3On := map[string]model.StringMap{
user3.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON,
},
}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap3On)
if len(mentions) == 0 {
t.Fatal("should've not returned any keywords")
}
// user with all types of mentions enabled
user4 := &model.User{
Id: model.NewId(),
@@ -687,8 +744,15 @@ func TestGetMentionKeywords(t *testing.T) {
},
}
// Channel-wide mentions are not ignored on channel level
channelMemberNotifyPropsMap4Off := map[string]model.StringMap{
user4.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
}
profiles = map[string]*model.User{user4.Id: user4}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true)
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off)
if len(mentions) != 6 {
t.Fatal("should've returned six mention keywords")
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
@@ -705,6 +769,25 @@ func TestGetMentionKeywords(t *testing.T) {
t.Fatal("should've returned mention key of @all")
}
// Channel-wide mentions are ignored on channel level
channelMemberNotifyPropsMap4On := map[string]model.StringMap{
user4.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_ON,
},
}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4On)
if len(mentions) != 4 {
t.Fatal("should've returned four mention keywords")
} else if ids, ok := mentions["user"]; !ok || ids[0] != user4.Id {
t.Fatal("should've returned mention key of user")
} else if ids, ok := mentions["@user"]; !ok || ids[0] != user4.Id {
t.Fatal("should've returned mention key of @user")
} else if ids, ok := mentions["mention"]; !ok || ids[0] != user4.Id {
t.Fatal("should've returned mention key of mention")
} else if ids, ok := mentions["First"]; !ok || ids[0] != user4.Id {
t.Fatal("should've returned mention key of First")
}
dup_count := func(list []string) map[string]int {
duplicate_frequency := make(map[string]int)
@@ -731,7 +814,22 @@ func TestGetMentionKeywords(t *testing.T) {
user3.Id: user3,
user4.Id: user4,
}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true)
// Channel-wide mentions are not ignored on channel level for all users
channelMemberNotifyPropsMap5Off := map[string]model.StringMap{
user1.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
user2.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
user3.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
user4.Id: {
"ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF,
},
}
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap5Off)
if len(mentions) != 6 {
t.Fatal("should've returned six mention keywords")
} else if ids, ok := mentions["user"]; !ok || len(ids) != 2 || (ids[0] != user1.Id && ids[1] != user1.Id) || (ids[0] != user4.Id && ids[1] != user4.Id) {
@@ -750,7 +848,7 @@ func TestGetMentionKeywords(t *testing.T) {
// multiple users and more than MaxNotificationsPerChannel
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxNotificationsPerChannel = 3 })
mentions = th.App.GetMentionKeywordsInChannel(profiles, true)
mentions = th.App.GetMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMap4Off)
if len(mentions) != 4 {
t.Fatal("should've returned four mention keywords")
} else if _, ok := mentions["@channel"]; ok {
@@ -765,7 +863,7 @@ func TestGetMentionKeywords(t *testing.T) {
profiles = map[string]*model.User{
user1.Id: user1,
}
mentions = th.App.GetMentionKeywordsInChannel(profiles, false)
mentions = th.App.GetMentionKeywordsInChannel(profiles, false, channelMemberNotifyPropsMap4Off)
if len(mentions) != 3 {
t.Fatal("should've returned three mention keywords")
} else if ids, ok := mentions["user"]; !ok || len(ids) != 1 || ids[0] != user1.Id {

Просмотреть файл

@@ -601,7 +601,7 @@ func (a *App) CompleteSwitchWithOAuth(service string, userData io.ReadCloser, em
return nil, result.Err
}
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
@@ -859,7 +859,7 @@ func (a *App) SwitchOAuthToEmail(email, password, requesterId string) (string, *
T := utils.GetUserTranslations(user.Locale)
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}

Просмотреть файл

@@ -17,11 +17,11 @@ func StoreOverride(override interface{}) Option {
return func(a *App) {
switch o := override.(type) {
case store.Store:
a.newStore = func() store.Store {
a.Srv.newStore = func() store.Store {
return o
}
case func(*App) store.Store:
a.newStore = func() store.Store {
a.Srv.newStore = func() store.Store {
return o(a)
}
default:
@@ -32,10 +32,10 @@ func StoreOverride(override interface{}) Option {
func ConfigFile(file string) Option {
return func(a *App) {
a.configFile = file
a.Srv.configFile = file
}
}
func DisableConfigWatch(a *App) {
a.disableConfigWatch = true
a.Srv.disableConfigWatch = true
}

Просмотреть файл

@@ -16,21 +16,21 @@ import (
)
func (a *App) SyncPluginsActiveState() {
if a.Plugins == nil {
if a.Srv.Plugins == nil {
return
}
config := a.Config().PluginSettings
if *config.Enable {
availablePlugins, err := a.Plugins.Available()
availablePlugins, err := a.Srv.Plugins.Available()
if err != nil {
a.Log.Error("Unable to get available plugins", mlog.Err(err))
return
}
// Deactivate any plugins that have been disabled.
for _, plugin := range a.Plugins.Active() {
for _, plugin := range a.Srv.Plugins.Active() {
// Determine if plugin is enabled
pluginId := plugin.Manifest.Id
pluginEnabled := false
@@ -40,7 +40,7 @@ func (a *App) SyncPluginsActiveState() {
// If it's not enabled we need to deactivate it
if !pluginEnabled {
deactivated := a.Plugins.Deactivate(pluginId)
deactivated := a.Srv.Plugins.Deactivate(pluginId)
if deactivated && plugin.Manifest.HasClient() {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DISABLED, "", "", "", nil)
message.Add("manifest", plugin.Manifest.ClientManifest())
@@ -65,7 +65,7 @@ func (a *App) SyncPluginsActiveState() {
// Activate plugin if enabled
if pluginEnabled {
updatedManifest, activated, err := a.Plugins.Activate(pluginId)
updatedManifest, activated, err := a.Srv.Plugins.Activate(pluginId)
if err != nil {
plugin.WrapLogger(a.Log).Error("Unable to activate plugin", mlog.Err(err))
continue
@@ -79,7 +79,7 @@ func (a *App) SyncPluginsActiveState() {
}
}
} else { // If plugins are disabled, shutdown plugins.
a.Plugins.Shutdown()
a.Srv.Plugins.Shutdown()
}
if err := a.notifyPluginStatusesChanged(); err != nil {
@@ -92,7 +92,7 @@ func (a *App) NewPluginAPI(manifest *model.Manifest) plugin.API {
}
func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
if a.Plugins != nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins != nil || !*a.Config().PluginSettings.Enable {
a.SyncPluginsActiveState()
return
}
@@ -113,7 +113,7 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
mlog.Error("Failed to start up plugins", mlog.Err(err))
return
} else {
a.Plugins = env
a.Srv.Plugins = env
}
prepackagedPluginsDir, found := utils.FindDir("prepackaged_plugins")
@@ -136,10 +136,10 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
}
// Sync plugin active state when config changes. Also notify plugins.
a.RemoveConfigListener(a.PluginConfigListenerId)
a.PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) {
a.RemoveConfigListener(a.Srv.PluginConfigListenerId)
a.Srv.PluginConfigListenerId = a.AddConfigListener(func(*model.Config, *model.Config) {
a.SyncPluginsActiveState()
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.OnConfigurationChange()
return true
}, plugin.OnConfigurationChangeId)
@@ -150,25 +150,25 @@ func (a *App) InitPlugins(pluginDir, webappPluginDir string) {
}
func (a *App) ShutDownPlugins() {
if a.Plugins == nil {
if a.Srv.Plugins == nil {
return
}
mlog.Info("Shutting down plugins")
a.Plugins.Shutdown()
a.Srv.Plugins.Shutdown()
a.RemoveConfigListener(a.PluginConfigListenerId)
a.PluginConfigListenerId = ""
a.Plugins = nil
a.RemoveConfigListener(a.Srv.PluginConfigListenerId)
a.Srv.PluginConfigListenerId = ""
a.Srv.Plugins = nil
}
func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return nil, model.NewAppError("GetActivePluginManifests", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins := a.Plugins.Active()
plugins := a.Srv.Plugins.Active()
manifests := make([]*model.Manifest, len(plugins))
for i, plugin := range plugins {
@@ -181,11 +181,11 @@ func (a *App) GetActivePluginManifests() ([]*model.Manifest, *model.AppError) {
// EnablePlugin will set the config for an installed plugin to enabled, triggering asynchronous
// activation if inactive anywhere in the cluster.
func (a *App) EnablePlugin(id string) *model.AppError {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return model.NewAppError("EnablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins, err := a.Plugins.Available()
plugins, err := a.Srv.Plugins.Available()
if err != nil {
return model.NewAppError("EnablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -221,11 +221,11 @@ func (a *App) EnablePlugin(id string) *model.AppError {
// DisablePlugin will set the config for an installed plugin to disabled, triggering deactivation if active.
func (a *App) DisablePlugin(id string) *model.AppError {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return model.NewAppError("DisablePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins, err := a.Plugins.Available()
plugins, err := a.Srv.Plugins.Available()
if err != nil {
return model.NewAppError("DisablePlugin", "app.plugin.config.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -256,7 +256,7 @@ func (a *App) DisablePlugin(id string) *model.AppError {
}
func (a *App) PluginsReady() bool {
return a.Plugins != nil && *a.Config().PluginSettings.Enable
return a.Srv.Plugins != nil && *a.Config().PluginSettings.Enable
}
func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
@@ -264,7 +264,7 @@ func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
return nil, model.NewAppError("GetPlugins", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
availablePlugins, err := a.Plugins.Available()
availablePlugins, err := a.Srv.Plugins.Available()
if err != nil {
return nil, model.NewAppError("GetPlugins", "app.plugin.get_plugins.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -278,7 +278,7 @@ func (a *App) GetPlugins() (*model.PluginsResponse, *model.AppError) {
Manifest: *plugin.Manifest,
}
if a.Plugins.IsActive(plugin.Manifest.Id) {
if a.Srv.Plugins.IsActive(plugin.Manifest.Id) {
resp.Active = append(resp.Active, info)
} else {
resp.Inactive = append(resp.Inactive, info)

Просмотреть файл

@@ -107,6 +107,10 @@ func (api *PluginAPI) GetTeamByName(name string) (*model.Team, *model.AppError)
return api.app.GetTeamByName(name)
}
func (api *PluginAPI) GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError) {
return api.app.GetTeamsUnreadForUser("", userId)
}
func (api *PluginAPI) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) {
return api.app.UpdateTeam(team)
}
@@ -385,6 +389,10 @@ func (api *PluginAPI) GetProfileImage(userId string) ([]byte, *model.AppError) {
return data, err
}
func (api *PluginAPI) GetEmojiList(sortBy string, page, perPage int) ([]*model.Emoji, *model.AppError) {
return api.app.GetEmojiList(page, perPage, sortBy)
}
func (api *PluginAPI) GetEmojiByName(name string) (*model.Emoji, *model.AppError) {
return api.app.GetEmojiByName(name)
}
@@ -422,10 +430,49 @@ func (api *PluginAPI) ReadFile(path string) ([]byte, *model.AppError) {
return api.app.ReadFile(path)
}
func (api *PluginAPI) UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) {
return api.app.UploadFile(data, channelId, filename)
}
func (api *PluginAPI) GetEmojiImage(emojiId string) ([]byte, string, *model.AppError) {
return api.app.GetEmojiImage(emojiId)
}
// Plugin Section
func (api *PluginAPI) GetPlugins() ([]*model.Manifest, *model.AppError) {
plugins, err := api.app.GetPlugins()
if err != nil {
return nil, err
}
var manifests []*model.Manifest
for _, manifest := range plugins.Active {
manifests = append(manifests, &manifest.Manifest)
}
for _, manifest := range plugins.Inactive {
manifests = append(manifests, &manifest.Manifest)
}
return manifests, nil
}
func (api *PluginAPI) EnablePlugin(id string) *model.AppError {
return api.app.EnablePlugin(id)
}
func (api *PluginAPI) DisablePlugin(id string) *model.AppError {
return api.app.DisablePlugin(id)
}
func (api *PluginAPI) RemovePlugin(id string) *model.AppError {
return api.app.RemovePlugin(id)
}
func (api *PluginAPI) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) {
return api.app.GetPluginStatus(id)
}
// KV Store Section
func (api *PluginAPI) KVSet(key string, value []byte) *model.AppError {
return api.app.SetPluginKey(api.id, key, value)
}

Просмотреть файл

@@ -5,6 +5,7 @@ package app
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -31,9 +32,12 @@ func setupPluginApiTest(t *testing.T, pluginCode string, pluginManifest string,
compileGo(t, pluginCode, backend)
ioutil.WriteFile(filepath.Join(pluginDir, pluginId, "plugin.json"), []byte(pluginManifest), 0600)
env.Activate(pluginId)
manifest, activated, reterr := env.Activate(pluginId)
require.Nil(t, reterr)
require.NotNil(t, manifest)
require.True(t, activated)
app.Plugins = env
app.Srv.Plugins = env
}
func TestPluginAPIUpdateUserStatus(t *testing.T) {
@@ -120,7 +124,7 @@ func TestPluginAPILoadPluginConfiguration(t *testing.T) {
}
]
}}`, "testloadpluginconfig", th.App)
hooks, err := th.App.Plugins.HooksForPlugin("testloadpluginconfig")
hooks, err := th.App.Srv.Plugins.HooksForPlugin("testloadpluginconfig")
assert.NoError(t, err)
_, ret := hooks.MessageWillBePosted(nil, nil)
assert.Equal(t, "str32true", ret)
@@ -194,7 +198,7 @@ func TestPluginAPILoadPluginConfigurationDefaults(t *testing.T) {
}
]
}}`, "testloadpluginconfig", th.App)
hooks, err := th.App.Plugins.HooksForPlugin("testloadpluginconfig")
hooks, err := th.App.Srv.Plugins.HooksForPlugin("testloadpluginconfig")
assert.NoError(t, err)
_, ret := hooks.MessageWillBePosted(nil, nil)
assert.Equal(t, "override35true", ret)
@@ -215,3 +219,61 @@ func TestPluginAPIGetProfileImage(t *testing.T) {
require.NotNil(t, err)
require.Nil(t, data)
}
func TestPluginAPIGetPlugins(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
api := th.SetupPluginAPI()
pluginCode := `
package main
import (
"github.com/mattermost/mattermost-server/plugin"
)
type MyPlugin struct {
plugin.MattermostPlugin
}
func main() {
plugin.ClientMain(&MyPlugin{})
}
`
pluginDir, err := ioutil.TempDir("", "")
require.NoError(t, err)
webappPluginDir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(pluginDir)
defer os.RemoveAll(webappPluginDir)
env, err := plugin.NewEnvironment(th.App.NewPluginAPI, pluginDir, webappPluginDir, th.App.Log)
require.NoError(t, err)
pluginIDs := []string{"pluginid1", "pluginid2", "pluginid3"}
var pluginManifests []*model.Manifest
for _, pluginID := range pluginIDs {
backend := filepath.Join(pluginDir, pluginID, "backend.exe")
compileGo(t, pluginCode, backend)
ioutil.WriteFile(filepath.Join(pluginDir, pluginID, "plugin.json"), []byte(fmt.Sprintf(`{"id": "%s", "server": {"executable": "backend.exe"}}`, pluginID)), 0600)
manifest, activated, reterr := env.Activate(pluginID)
require.Nil(t, reterr)
require.NotNil(t, manifest)
require.True(t, activated)
pluginManifests = append(pluginManifests, manifest)
}
th.App.Srv.Plugins = env
// Decative the last one for testing
sucess := env.Deactivate(pluginIDs[len(pluginIDs)-1])
require.True(t, sucess)
// check existing user first
plugins, err := api.GetPlugins()
assert.Nil(t, err)
assert.NotEmpty(t, plugins)
assert.Equal(t, pluginManifests, plugins)
}

Просмотреть файл

@@ -31,10 +31,10 @@ func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) err
DisplayName: command.DisplayName,
}
a.pluginCommandsLock.Lock()
defer a.pluginCommandsLock.Unlock()
a.Srv.pluginCommandsLock.Lock()
defer a.Srv.pluginCommandsLock.Unlock()
for _, pc := range a.pluginCommands {
for _, pc := range a.Srv.pluginCommands {
if pc.Command.Trigger == command.Trigger && pc.Command.TeamId == command.TeamId {
if pc.PluginId == pluginId {
pc.Command = command
@@ -43,7 +43,7 @@ func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) err
}
}
a.pluginCommands = append(a.pluginCommands, &PluginCommand{
a.Srv.pluginCommands = append(a.Srv.pluginCommands, &PluginCommand{
Command: command,
PluginId: pluginId,
})
@@ -53,37 +53,37 @@ func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) err
func (a *App) UnregisterPluginCommand(pluginId, teamId, trigger string) {
trigger = strings.ToLower(trigger)
a.pluginCommandsLock.Lock()
defer a.pluginCommandsLock.Unlock()
a.Srv.pluginCommandsLock.Lock()
defer a.Srv.pluginCommandsLock.Unlock()
var remaining []*PluginCommand
for _, pc := range a.pluginCommands {
for _, pc := range a.Srv.pluginCommands {
if pc.Command.TeamId != teamId || pc.Command.Trigger != trigger {
remaining = append(remaining, pc)
}
}
a.pluginCommands = remaining
a.Srv.pluginCommands = remaining
}
func (a *App) UnregisterPluginCommands(pluginId string) {
a.pluginCommandsLock.Lock()
defer a.pluginCommandsLock.Unlock()
a.Srv.pluginCommandsLock.Lock()
defer a.Srv.pluginCommandsLock.Unlock()
var remaining []*PluginCommand
for _, pc := range a.pluginCommands {
for _, pc := range a.Srv.pluginCommands {
if pc.PluginId != pluginId {
remaining = append(remaining, pc)
}
}
a.pluginCommands = remaining
a.Srv.pluginCommands = remaining
}
func (a *App) PluginCommandsForTeam(teamId string) []*model.Command {
a.pluginCommandsLock.RLock()
defer a.pluginCommandsLock.RUnlock()
a.Srv.pluginCommandsLock.RLock()
defer a.Srv.pluginCommandsLock.RUnlock()
var commands []*model.Command
for _, pc := range a.pluginCommands {
for _, pc := range a.Srv.pluginCommands {
if pc.Command.TeamId == "" || pc.Command.TeamId == teamId {
commands = append(commands, pc.Command)
}
@@ -96,12 +96,12 @@ func (a *App) ExecutePluginCommand(args *model.CommandArgs) (*model.Command, *mo
trigger := parts[0][1:]
trigger = strings.ToLower(trigger)
a.pluginCommandsLock.RLock()
defer a.pluginCommandsLock.RUnlock()
a.Srv.pluginCommandsLock.RLock()
defer a.Srv.pluginCommandsLock.RUnlock()
for _, pc := range a.pluginCommands {
for _, pc := range a.Srv.pluginCommands {
if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger {
pluginHooks, err := a.Plugins.HooksForPlugin(pc.PluginId)
pluginHooks, err := a.Srv.Plugins.HooksForPlugin(pc.PluginId)
if err != nil {
return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}

Просмотреть файл

@@ -44,7 +44,7 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a
env, err := plugin.NewEnvironment(apiFunc, pluginDir, webappPluginDir, app.Log)
require.NoError(t, err)
app.Plugins = env
app.Srv.Plugins = env
pluginIds := []string{}
activationErrors := []error{}
for _, code := range pluginCode {

Просмотреть файл

@@ -22,7 +22,7 @@ func (a *App) InstallPlugin(pluginFile io.Reader, replace bool) (*model.Manifest
}
func (a *App) installPlugin(pluginFile io.Reader, replace bool) (*model.Manifest, *model.AppError) {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return nil, model.NewAppError("installPlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
@@ -55,7 +55,7 @@ func (a *App) installPlugin(pluginFile io.Reader, replace bool) (*model.Manifest
return nil, model.NewAppError("installPlugin", "app.plugin.invalid_id.app_error", map[string]interface{}{"Min": plugin.MinIdLength, "Max": plugin.MaxIdLength, "Regex": plugin.ValidIdRegex}, "", http.StatusBadRequest)
}
bundles, err := a.Plugins.Available()
bundles, err := a.Srv.Plugins.Available()
if err != nil {
return nil, model.NewAppError("installPlugin", "app.plugin.install.app_error", nil, err.Error(), http.StatusInternalServerError)
}
@@ -91,11 +91,11 @@ func (a *App) RemovePlugin(id string) *model.AppError {
}
func (a *App) removePlugin(id string) *model.AppError {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return model.NewAppError("removePlugin", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
plugins, err := a.Plugins.Available()
plugins, err := a.Srv.Plugins.Available()
if err != nil {
return model.NewAppError("removePlugin", "app.plugin.deactivate.app_error", nil, err.Error(), http.StatusBadRequest)
}
@@ -114,13 +114,13 @@ func (a *App) removePlugin(id string) *model.AppError {
return model.NewAppError("removePlugin", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
}
if a.Plugins.IsActive(id) && manifest.HasClient() {
if a.Srv.Plugins.IsActive(id) && manifest.HasClient() {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_DISABLED, "", "", "", nil)
message.Add("manifest", manifest.ClientManifest())
a.Publish(message)
}
a.Plugins.Deactivate(id)
a.Srv.Plugins.Deactivate(id)
a.UnregisterPluginCommands(id)
err = os.RemoveAll(pluginPath)

Просмотреть файл

@@ -9,16 +9,17 @@ import (
"strings"
"bytes"
"io/ioutil"
"github.com/gorilla/mux"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/utils"
"io/ioutil"
)
func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
err := model.NewAppError("ServePluginRequest", "app.plugin.disabled.app_error", nil, "Enable plugins to serve plugin requests", http.StatusNotImplemented)
a.Log.Error(err.Error())
w.WriteHeader(err.StatusCode)
@@ -28,7 +29,7 @@ func (a *App) ServePluginRequest(w http.ResponseWriter, r *http.Request) {
}
params := mux.Vars(r)
hooks, err := a.Plugins.HooksForPlugin(params["plugin_id"])
hooks, err := a.Srv.Plugins.HooksForPlugin(params["plugin_id"])
if err != nil {
a.Log.Error("Access to route for non-existent plugin", mlog.String("missing_plugin_id", params["plugin_id"]), mlog.Err(err))
http.NotFound(w, r)

Просмотреть файл

@@ -9,13 +9,34 @@ import (
"github.com/mattermost/mattermost-server/model"
)
// GetPluginStatus returns the status for a plugin installed on this server.
func (a *App) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return nil, model.NewAppError("GetPluginStatus", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
pluginStatuses, err := a.Srv.Plugins.Statuses()
if err != nil {
return nil, model.NewAppError("GetPluginStatus", "app.plugin.get_statuses.app_error", nil, err.Error(), http.StatusInternalServerError)
}
// Add our cluster ID
for _, status := range pluginStatuses {
if status.PluginId == id {
status.ClusterId = a.GetClusterId()
return status, nil
}
}
return nil, model.NewAppError("GetPluginStatus", "app.plugin.not_installed.app_error", nil, "", http.StatusBadRequest)
}
// GetPluginStatuses returns the status for plugins installed on this server.
func (a *App) GetPluginStatuses() (model.PluginStatuses, *model.AppError) {
if a.Plugins == nil || !*a.Config().PluginSettings.Enable {
if a.Srv.Plugins == nil || !*a.Config().PluginSettings.Enable {
return nil, model.NewAppError("GetPluginStatuses", "app.plugin.disabled.app_error", nil, "", http.StatusNotImplemented)
}
pluginStatuses, err := a.Plugins.Statuses()
pluginStatuses, err := a.Srv.Plugins.Statuses()
if err != nil {
return nil, model.NewAppError("GetPluginStatuses", "app.plugin.get_statuses.app_error", nil, err.Error(), http.StatusInternalServerError)
}

Просмотреть файл

@@ -146,7 +146,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
if a.PluginsReady() {
var rejectionError *model.AppError
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
replacementPost, rejectionReason := hooks.MessageWillBePosted(pluginContext, post)
if rejectionReason != "" {
rejectionError = model.NewAppError("createPost", "Post rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest)
@@ -170,9 +170,9 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
rpost := result.Data.(*model.Post)
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.MessageHasBeenPosted(pluginContext, rpost)
return true
}, plugin.MessageHasBeenPostedId)
@@ -181,7 +181,7 @@ func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhoo
esInterface := a.Elasticsearch
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing {
a.Go(func() {
a.Srv.Go(func() {
esInterface.IndexPost(rpost, channel.TeamId)
})
}
@@ -273,7 +273,7 @@ func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *mode
}
if triggerWebhooks {
a.Go(func() {
a.Srv.Go(func() {
if err := a.handleWebhookEvents(post, team, channel, user); err != nil {
mlog.Error(err.Error())
}
@@ -363,7 +363,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
if a.PluginsReady() {
var rejectionReason string
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
newPost, rejectionReason = hooks.MessageWillBeUpdated(pluginContext, newPost, oldPost)
return post != nil
}, plugin.MessageWillBeUpdatedId)
@@ -379,9 +379,9 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
rpost := result.Data.(*model.Post)
if a.PluginsReady() {
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.MessageHasBeenUpdated(pluginContext, newPost, oldPost)
return true
}, plugin.MessageHasBeenUpdatedId)
@@ -390,7 +390,7 @@ func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model
esInterface := a.Elasticsearch
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing {
a.Go(func() {
a.Srv.Go(func() {
rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id)
if rchannel.Err != nil {
mlog.Error(fmt.Sprintf("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id))
@@ -578,16 +578,16 @@ func (a *App) DeletePost(postId, deleteByID string) (*model.Post, *model.AppErro
message.Add("post", clientPost.ToJson())
a.Publish(message)
a.Go(func() {
a.Srv.Go(func() {
a.DeletePostFiles(post)
})
a.Go(func() {
a.Srv.Go(func() {
a.DeleteFlaggedPosts(post.Id)
})
esInterface := a.Elasticsearch
if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing {
a.Go(func() {
a.Srv.Go(func() {
esInterface.DeletePost(post)
})
}

Просмотреть файл

@@ -528,9 +528,9 @@ func TestMaxPostSize(t *testing.T) {
app := App{
Srv: &Server{
Store: mockStore,
Store: mockStore,
config: atomic.Value{},
},
config: atomic.Value{},
}
assert.Equal(t, testCase.ExpectedMaxPostSize, app.MaxPostSize())

Просмотреть файл

@@ -46,7 +46,7 @@ func (a *App) SaveReactionForPost(reaction *model.Reaction) (*model.Reaction, *m
// The post is always modified since the UpdateAt always changes
a.InvalidateCacheForChannelPosts(post.ChannelId)
a.Go(func() {
a.Srv.Go(func() {
a.sendReactionEvent(model.WEBSOCKET_EVENT_REACTION_ADDED, reaction, post, true)
})
@@ -99,7 +99,7 @@ func (a *App) DeleteReactionForPost(reaction *model.Reaction) *model.AppError {
// The post is always modified since the UpdateAt always changes
a.InvalidateCacheForChannelPosts(post.ChannelId)
a.Go(func() {
a.Srv.Go(func() {
a.sendReactionEvent(model.WEBSOCKET_EVENT_REACTION_REMOVED, reaction, post, hasReactions)
})

Просмотреть файл

@@ -104,7 +104,7 @@ func (a *App) sendUpdatedRoleEvent(role *model.Role) {
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_ROLE_UPDATED, "", "", "", nil)
message.Add("role", role.ToJson())
a.Go(func() {
a.Srv.Go(func() {
a.Publish(message)
})
}

Просмотреть файл

@@ -152,7 +152,7 @@ func (a *App) GetChannelsForScheme(scheme *model.Scheme, offset int, limit int)
}
func (a *App) IsPhase2MigrationCompleted() *model.AppError {
if a.phase2PermissionsMigrationComplete {
if a.Srv.phase2PermissionsMigrationComplete {
return nil
}
@@ -160,7 +160,7 @@ func (a *App) IsPhase2MigrationCompleted() *model.AppError {
return model.NewAppError("App.IsPhase2MigrationCompleted", "app.schemes.is_phase_2_migration_completed.not_completed.app_error", nil, result.Err.Error(), http.StatusNotImplemented)
}
a.phase2PermissionsMigrationComplete = true
a.Srv.phase2PermissionsMigrationComplete = true
return nil
}

Просмотреть файл

@@ -5,6 +5,7 @@ package app
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"fmt"
"io"
@@ -14,16 +15,21 @@ import (
"net/url"
"os"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/pkg/errors"
"github.com/rs/cors"
"github.com/throttled/throttled"
"golang.org/x/crypto/acme/autocert"
"github.com/mattermost/mattermost-server/jobs"
"github.com/mattermost/mattermost-server/mlog"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/store"
"github.com/mattermost/mattermost-server/utils"
)
@@ -44,6 +50,79 @@ type Server struct {
RateLimiter *RateLimiter
didFinishListen chan struct{}
goroutineCount int32
goroutineExitSignal chan struct{}
Plugins *plugin.Environment
PluginConfigListenerId string
EmailBatching *EmailBatchingJob
EmailRateLimiter *throttled.GCRARateLimiter
Hubs []*Hub
HubsStopCheckingForDeadlock chan bool
PushNotificationsHub PushNotificationsHub
Jobs *jobs.JobServer
config atomic.Value
envConfig map[string]interface{}
configFile string
configListeners map[string]func(*model.Config, *model.Config)
clusterLeaderListeners sync.Map
licenseValue atomic.Value
clientLicenseValue atomic.Value
licenseListeners map[string]func()
timezones atomic.Value
newStore func() store.Store
htmlTemplateWatcher *utils.HTMLTemplateWatcher
sessionCache *utils.Cache
configListenerId string
licenseListenerId string
logListenerId string
clusterLeaderListenerId string
disableConfigWatch bool
configWatcher *utils.ConfigWatcher
asymmetricSigningKey *ecdsa.PrivateKey
pluginCommands []*PluginCommand
pluginCommandsLock sync.RWMutex
clientConfig map[string]string
clientConfigHash string
limitedClientConfig map[string]string
diagnosticId string
phase2PermissionsMigrationComplete bool
}
// Go creates a goroutine, but maintains a record of it to ensure that execution completes before
// the app is destroyed.
func (s *Server) Go(f func()) {
atomic.AddInt32(&s.goroutineCount, 1)
go func() {
f()
atomic.AddInt32(&s.goroutineCount, -1)
select {
case s.goroutineExitSignal <- struct{}{}:
default:
}
}()
}
// WaitForGoroutines blocks until all goroutines created by App.Go exit.
func (s *Server) WaitForGoroutines() {
for atomic.LoadInt32(&s.goroutineCount) != 0 {
<-s.goroutineExitSignal
}
}
var corsAllowedMethods = []string{

Просмотреть файл

@@ -5,13 +5,14 @@ package app
import (
"crypto/tls"
"github.com/mattermost/mattermost-server/utils"
"net/http"
"path"
"strconv"
"strings"
"testing"
"github.com/mattermost/mattermost-server/utils"
"github.com/mattermost/mattermost-server/model"
"github.com/stretchr/testify/require"
)
@@ -24,7 +25,7 @@ func TestStartServerSuccess(t *testing.T) {
serverErr := a.StartServer()
client := &http.Client{}
checkEndpoint(t, client, "http://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
checkEndpoint(t, client, "http://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
a.Shutdown()
require.NoError(t, serverErr)
@@ -77,7 +78,7 @@ func TestStartServerTLSSuccess(t *testing.T) {
}
client := &http.Client{Transport: tr}
checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
a.Shutdown()
require.NoError(t, serverErr)
@@ -100,12 +101,12 @@ func TestStartServerTLSVersion(t *testing.T) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS11,
MaxVersion: tls.VersionTLS11,
},
}
client := &http.Client{Transport: tr}
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
if !strings.Contains(err.Error(), "remote error: tls: protocol version not supported") {
t.Errorf("Expected protocol version error, got %s", err)
@@ -117,7 +118,7 @@ func TestStartServerTLSVersion(t *testing.T) {
},
}
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
if err != nil {
t.Errorf("Expected nil, got %s", err)
@@ -154,7 +155,7 @@ func TestStartServerTLSOverwriteCipher(t *testing.T) {
}
client := &http.Client{Transport: tr}
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
if !strings.Contains(err.Error(), "remote error: tls: handshake failure") {
t.Errorf("Expected protocol version error, got %s", err)
@@ -170,7 +171,7 @@ func TestStartServerTLSOverwriteCipher(t *testing.T) {
},
}
err = checkEndpoint(t, client, "https://localhost:" + strconv.Itoa(a.Srv.ListenAddr.Port) + "/", http.StatusNotFound)
err = checkEndpoint(t, client, "https://localhost:"+strconv.Itoa(a.Srv.ListenAddr.Port)+"/", http.StatusNotFound)
if err != nil {
t.Errorf("Expected nil, got %s", err)

Просмотреть файл

@@ -29,7 +29,7 @@ func (a *App) GetSession(token string) (*model.Session, *model.AppError) {
metrics := a.Metrics
var session *model.Session
if ts, ok := a.sessionCache.Get(token); ok {
if ts, ok := a.Srv.sessionCache.Get(token); ok {
session = ts.(*model.Session)
if metrics != nil {
metrics.IncrementMemCacheHitCounterSession()
@@ -137,13 +137,13 @@ func (a *App) ClearSessionCacheForUser(userId string) {
}
func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
keys := a.sessionCache.Keys()
keys := a.Srv.sessionCache.Keys()
for _, key := range keys {
if ts, ok := a.sessionCache.Get(key); ok {
if ts, ok := a.Srv.sessionCache.Get(key); ok {
session := ts.(*model.Session)
if session.UserId == userId {
a.sessionCache.Remove(key)
a.Srv.sessionCache.Remove(key)
if a.Metrics != nil {
a.Metrics.IncrementMemCacheInvalidationCounterSession()
}
@@ -155,11 +155,11 @@ func (a *App) ClearSessionCacheForUserSkipClusterSend(userId string) {
}
func (a *App) AddSessionToCache(session *model.Session) {
a.sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*a.Config().ServiceSettings.SessionCacheInMinutes*60))
a.Srv.sessionCache.AddWithExpiresInSecs(session.Token, session, int64(*a.Config().ServiceSettings.SessionCacheInMinutes*60))
}
func (a *App) SessionCacheLength() int {
return a.sessionCache.Len()
return a.Srv.sessionCache.Len()
}
func (a *App) RevokeSessionsForDeviceId(userId string, deviceId string, currentSessionId string) *model.AppError {

Просмотреть файл

@@ -22,16 +22,16 @@ func TestCache(t *testing.T) {
UserId: model.NewId(),
}
th.App.sessionCache.AddWithExpiresInSecs(session.Token, session, 5*60)
th.App.Srv.sessionCache.AddWithExpiresInSecs(session.Token, session, 5*60)
keys := th.App.sessionCache.Keys()
keys := th.App.Srv.sessionCache.Keys()
if len(keys) <= 0 {
t.Fatal("should have items")
}
th.App.ClearSessionCacheForUser(session.UserId)
rkeys := th.App.sessionCache.Keys()
rkeys := th.App.Srv.sessionCache.Keys()
if len(rkeys) != len(keys)-1 {
t.Fatal("should have one less")
}

Просмотреть файл

@@ -466,9 +466,9 @@ func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId
actor, _ = a.GetUser(userRequestorId)
}
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasJoinedTeam(pluginContext, tm, actor)
return true
}, plugin.UserHasJoinedTeamId)
@@ -789,9 +789,9 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string)
actor, _ = a.GetUser(requestorId)
}
a.Go(func() {
a.Srv.Go(func() {
pluginContext := &plugin.Context{}
a.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
a.Srv.Plugins.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
hooks.UserHasLeftTeam(pluginContext, teamMember, actor)
return true
}, plugin.UserHasLeftTeamId)

Просмотреть файл

@@ -9,7 +9,7 @@ import (
)
func (a *App) Timezones() model.SupportedTimezones {
if cfg := a.timezones.Load(); cfg != nil {
if cfg := a.Srv.timezones.Load(); cfg != nil {
return cfg.(model.SupportedTimezones)
}
return model.SupportedTimezones{}
@@ -24,5 +24,5 @@ func (a *App) LoadTimezones() {
timezoneCfg := utils.LoadTimezones(timezonePath)
a.timezones.Store(timezoneCfg)
a.Srv.timezones.Store(timezoneCfg)
}

Просмотреть файл

@@ -1034,14 +1034,14 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
if sendNotifications {
if rusers[0].Email != rusers[1].Email {
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendEmailChangeEmail(rusers[1].Email, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
})
if a.Config().EmailSettings.RequireEmailVerification {
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendEmailVerification(rusers[0]); err != nil {
mlog.Error(err.Error())
}
@@ -1050,7 +1050,7 @@ func (a *App) UpdateUser(user *model.User, sendNotifications bool) (*model.User,
}
if rusers[0].Username != rusers[1].Username {
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendChangeUsernameEmail(rusers[1].Username, rusers[0].Username, rusers[0].Email, rusers[0].Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
@@ -1090,7 +1090,7 @@ func (a *App) UpdateMfa(activate bool, userId, token string) *model.AppError {
}
}
a.Go(func() {
a.Srv.Go(func() {
user, err := a.GetUser(userId)
if err != nil {
mlog.Error(err.Error())
@@ -1133,7 +1133,7 @@ func (a *App) UpdatePasswordSendEmail(user *model.User, newPassword, method stri
return err
}
a.Go(func() {
a.Srv.Go(func() {
if err := a.SendPasswordChangeEmail(user.Email, method, user.Locale, a.GetSiteURL()); err != nil {
mlog.Error(err.Error())
}
@@ -1622,22 +1622,3 @@ func (a *App) UpdateOAuthUserAttrs(userData io.Reader, user *model.User, provide
return nil
}
func (a *App) RecordUserTermsOfServiceAction(userId, termsOfServiceId string, accepted bool) *model.AppError {
user, err := a.GetUser(userId)
if err != nil {
return err
}
if accepted {
user.AcceptedTermsOfServiceId = termsOfServiceId
} else {
user.AcceptedTermsOfServiceId = ""
}
_, err = a.UpdateUser(user, false)
if err != nil {
return err
}
return nil
}

33
app/user_terms_of_service.go Обычный файл
Просмотреть файл

@@ -0,0 +1,33 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import "github.com/mattermost/mattermost-server/model"
func (a *App) GetUserTermsOfService(userId string) (*model.UserTermsOfService, *model.AppError) {
if result := <-a.Srv.Store.UserTermsOfService().GetByUser(userId); result.Err != nil {
return nil, result.Err
} else {
return result.Data.(*model.UserTermsOfService), nil
}
}
func (a *App) SaveUserTermsOfService(userId, termsOfServiceId string, accepted bool) *model.AppError {
if accepted {
userTermsOfService := &model.UserTermsOfService{
UserId: userId,
TermsOfServiceId: termsOfServiceId,
}
if result := <-a.Srv.Store.UserTermsOfService().Save(userTermsOfService); result.Err != nil {
return result.Err
}
} else {
if result := <-a.Srv.Store.UserTermsOfService().Delete(userId, termsOfServiceId); result.Err != nil {
return result.Err
}
}
return nil
}

34
app/user_terms_of_service_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,34 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestUserTermsOfService(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
userTermsOfService, err := th.App.GetUserTermsOfService(th.BasicUser.Id)
checkError(t, err)
assert.Nil(t, userTermsOfService)
assert.Equal(t, "store.sql_user_terms_of_service.get_by_user.no_rows.app_error", err.Id)
termsOfService, err := th.App.CreateTermsOfService("terms of service", th.BasicUser.Id)
checkNoError(t, err)
err = th.App.SaveUserTermsOfService(th.BasicUser.Id, termsOfService.Id, true)
checkNoError(t, err)
userTermsOfService, err = th.App.GetUserTermsOfService(th.BasicUser.Id)
checkNoError(t, err)
assert.NotNil(t, userTermsOfService)
assert.NotEmpty(t, userTermsOfService)
assert.Equal(t, th.BasicUser.Id, userTermsOfService.UserId)
assert.Equal(t, termsOfService.Id, userTermsOfService.TermsOfServiceId)
assert.NotEmpty(t, userTermsOfService.CreateAt)
}

Просмотреть файл

@@ -544,43 +544,3 @@ func TestPermanentDeleteUser(t *testing.T) {
t.Fatal("GetFileInfo after DeleteUser is nil")
}
}
func TestRecordUserTermsOfServiceAction(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
user := &model.User{
Email: strings.ToLower(model.NewId()) + "success+test@example.com",
Nickname: "Luke Skywalker", // trying to bring balance to the "Force", one test user at a time
Username: "luke" + model.NewId(),
Password: "passwd1",
AuthService: "",
}
user, err := th.App.CreateUser(user)
if err != nil {
t.Fatalf("failed to create user: %v", err)
}
defer th.App.PermanentDeleteUser(user)
termsOfService, err := th.App.CreateTermsOfService("text", user.Id)
if err != nil {
t.Fatalf("failed to create terms of service: %v", err)
}
err = th.App.RecordUserTermsOfServiceAction(user.Id, termsOfService.Id, true)
if err != nil {
t.Fatalf("failed to record user action: %v", err)
}
nuser, err := th.App.GetUser(user.Id)
assert.Equal(t, termsOfService.Id, nuser.AcceptedTermsOfServiceId)
err = th.App.RecordUserTermsOfServiceAction(user.Id, termsOfService.Id, false)
if err != nil {
t.Fatalf("failed to record user action: %v", err)
}
nuser, err = th.App.GetUser(user.Id)
assert.Empty(t, nuser.AcceptedTermsOfServiceId)
}

Просмотреть файл

@@ -45,7 +45,7 @@ type WebConn struct {
func (a *App) NewWebConn(ws *websocket.Conn, session model.Session, t goi18n.TranslateFunc, locale string) *WebConn {
if len(session.UserId) > 0 {
a.Go(func() {
a.Srv.Go(func() {
a.SetStatusOnline(session.UserId, false)
a.UpdateLastActivityAtIfNeeded(session)
})
@@ -126,7 +126,7 @@ func (c *WebConn) readPump() {
c.WebSocket.SetPongHandler(func(string) error {
c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
if c.IsAuthenticated() {
c.App.Go(func() {
c.App.Srv.Go(func() {
c.App.SetStatusAwayIfNeeded(c.UserId, false)
})
}
@@ -212,7 +212,7 @@ func (c *WebConn) writePump() {
}
if c.App.Metrics != nil {
c.App.Go(func() {
c.App.Srv.Go(func() {
c.App.Metrics.IncrementWebSocketBroadcast(msg.EventType())
})
}

Просмотреть файл

@@ -62,7 +62,7 @@ func (a *App) NewWebHub() *Hub {
func (a *App) TotalWebsocketConnections() int {
count := int64(0)
for _, hub := range a.Hubs {
for _, hub := range a.Srv.Hubs {
count = count + atomic.LoadInt64(&hub.connectionCount)
}
@@ -74,13 +74,13 @@ func (a *App) HubStart() {
numberOfHubs := runtime.NumCPU() * 2
mlog.Info(fmt.Sprintf("Starting %v websocket hubs", numberOfHubs))
a.Hubs = make([]*Hub, numberOfHubs)
a.HubsStopCheckingForDeadlock = make(chan bool, 1)
a.Srv.Hubs = make([]*Hub, numberOfHubs)
a.Srv.HubsStopCheckingForDeadlock = make(chan bool, 1)
for i := 0; i < len(a.Hubs); i++ {
a.Hubs[i] = a.NewWebHub()
a.Hubs[i].connectionIndex = i
a.Hubs[i].Start()
for i := 0; i < len(a.Srv.Hubs); i++ {
a.Srv.Hubs[i] = a.NewWebHub()
a.Srv.Hubs[i].connectionIndex = i
a.Srv.Hubs[i].Start()
}
go func() {
@@ -93,7 +93,7 @@ func (a *App) HubStart() {
for {
select {
case <-ticker.C:
for _, hub := range a.Hubs {
for _, hub := range a.Srv.Hubs {
if len(hub.broadcast) >= DEADLOCK_WARN {
mlog.Error(fmt.Sprintf("Hub processing might be deadlock on hub %v goroutine %v with %v events in the buffer", hub.connectionIndex, hub.goroutineId, len(hub.broadcast)))
buf := make([]byte, 1<<16)
@@ -109,7 +109,7 @@ func (a *App) HubStart() {
}
}
case <-a.HubsStopCheckingForDeadlock:
case <-a.Srv.HubsStopCheckingForDeadlock:
return
}
}
@@ -120,27 +120,27 @@ func (a *App) HubStop() {
mlog.Info("stopping websocket hub connections")
select {
case a.HubsStopCheckingForDeadlock <- true:
case a.Srv.HubsStopCheckingForDeadlock <- true:
default:
mlog.Warn("We appear to have already sent the stop checking for deadlocks command")
}
for _, hub := range a.Hubs {
for _, hub := range a.Srv.Hubs {
hub.Stop()
}
a.Hubs = []*Hub{}
a.Srv.Hubs = []*Hub{}
}
func (a *App) GetHubForUserId(userId string) *Hub {
if len(a.Hubs) == 0 {
if len(a.Srv.Hubs) == 0 {
return nil
}
hash := fnv.New32a()
hash.Write([]byte(userId))
index := hash.Sum32() % uint32(len(a.Hubs))
return a.Hubs[index]
index := hash.Sum32() % uint32(len(a.Srv.Hubs))
return a.Srv.Hubs[index]
}
func (a *App) HubRegister(webConn *WebConn) {
@@ -190,7 +190,7 @@ func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) {
hub.Broadcast(message)
}
} else {
for _, hub := range a.Hubs {
for _, hub := range a.Srv.Hubs {
hub.Broadcast(message)
}
}
@@ -416,7 +416,7 @@ func (h *Hub) Start() {
conns := connections.ForUser(webCon.UserId)
if len(conns) == 0 {
h.app.Go(func() {
h.app.Srv.Go(func() {
h.app.SetStatusOffline(webCon.UserId, false)
})
} else {
@@ -427,7 +427,7 @@ func (h *Hub) Start() {
}
}
if h.app.IsUserAway(latestActivity) {
h.app.Go(func() {
h.app.Srv.Go(func() {
h.app.SetStatusLastActivityAt(webCon.UserId, latestActivity)
})
}

Просмотреть файл

@@ -80,7 +80,7 @@ func (a *App) handleWebhookEvents(post *model.Post, team *model.Team, channel *m
TriggerWord: triggerWord,
FileIds: strings.Join(post.FileIds, ","),
}
a.Go(func(hook *model.OutgoingWebhook) func() {
a.Srv.Go(func(hook *model.OutgoingWebhook) func() {
return func() {
a.TriggerWebhook(payload, hook, post, channel)
}
@@ -102,7 +102,7 @@ func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.
}
for _, url := range hook.CallbackURLs {
a.Go(func(url string) func() {
a.Srv.Go(func(url string) func() {
return func() {
req, _ := http.NewRequest("POST", url, body)
req.Header.Set("Content-Type", contentType)

Просмотреть файл

@@ -10,10 +10,11 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/model"
"net/http"
"net/http/httptest"
"time"
"github.com/mattermost/mattermost-server/model"
)
func TestCreateIncomingWebhookForChannel(t *testing.T) {

Просмотреть файл

@@ -55,7 +55,7 @@ func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketReque
return
}
wr.app.Go(func() {
wr.app.Srv.Go(func() {
wr.app.SetStatusOnline(session.UserId, false)
wr.app.UpdateLastActivityAtIfNeeded(*session)
})

Просмотреть файл

@@ -31,6 +31,7 @@ var ChannelRenameCmd = &cobra.Command{
Short: "Rename a channel",
Long: `Rename a channel.`,
Example: `" channel rename myteam:mychannel newchannelname --display_name "New Display Name"`,
Args: cobra.MinimumNArgs(2),
RunE: renameChannelCmdF,
}
@@ -48,6 +49,7 @@ var AddChannelUsersCmd = &cobra.Command{
Short: "Add users to channel",
Long: "Add some users to channel",
Example: " channel add myteam:mychannel user@example.com username",
Args: cobra.MinimumNArgs(2),
RunE: addChannelUsersCmdF,
}
@@ -58,6 +60,7 @@ var ArchiveChannelsCmd = &cobra.Command{
Archive a channel along with all related information including posts from the database.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel archive myteam:mychannel",
Args: cobra.MinimumNArgs(1),
RunE: archiveChannelsCmdF,
}
@@ -68,6 +71,7 @@ var DeleteChannelsCmd = &cobra.Command{
Permanently deletes a channel along with all related information including posts from the database.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel delete myteam:mychannel",
Args: cobra.MinimumNArgs(1),
RunE: deleteChannelsCmdF,
}
@@ -77,6 +81,7 @@ var ListChannelsCmd = &cobra.Command{
Long: `List all channels on specified teams.
Archived channels are appended with ' (archived)'.`,
Example: " channel list myteam",
Args: cobra.MinimumNArgs(1),
RunE: listChannelsCmdF,
}
@@ -87,6 +92,7 @@ var MoveChannelsCmd = &cobra.Command{
Validates that all users in the channel belong to the target team. Incoming/Outgoing webhooks are moved along with the channel.
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel move newteam oldteam:mychannel --username myusername",
Args: cobra.MinimumNArgs(2),
RunE: moveChannelsCmdF,
}
@@ -96,6 +102,7 @@ var RestoreChannelsCmd = &cobra.Command{
Long: `Restore a previously deleted channel
Channels can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel restore myteam:mychannel",
Args: cobra.MinimumNArgs(1),
RunE: restoreChannelsCmdF,
}
@@ -105,6 +112,7 @@ var ModifyChannelCmd = &cobra.Command{
Long: `Change the public/private type of a channel.
Channel can be specified by [team]:[channel]. ie. myteam:mychannel or by channel ID.`,
Example: " channel modify myteam:mychannel --private --username myusername",
Args: cobra.MinimumNArgs(1),
RunE: modifyChannelCmdF,
}
@@ -252,10 +260,6 @@ func addChannelUsersCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
channel := getChannelFromChannelArg(a, args[0])
if channel == nil {
return errors.New("Unable to find channel '" + args[0] + "'")
@@ -286,10 +290,6 @@ func archiveChannelsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel to archive.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
@@ -311,10 +311,6 @@ func deleteChannelsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel to delete.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string
@@ -352,10 +348,6 @@ func moveChannelsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Enter the destination team and at least one channel to move.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find destination team '" + args[0] + "'")
@@ -429,10 +421,6 @@ func listChannelsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one team.")
}
teams := getTeamsFromTeamArgs(a, args)
for i, team := range teams {
if team == nil {
@@ -464,10 +452,6 @@ func restoreChannelsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Enter at least one channel.")
}
channels := getChannelsFromChannelArgs(a, args)
for i, channel := range channels {
if channel == nil {
@@ -489,10 +473,6 @@ func modifyChannelCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) != 1 {
return errors.New("Enter at one channel to modify.")
}
username, erru := command.Flags().GetString("username")
if erru != nil || username == "" {
return errors.New("Username is required.")
@@ -535,10 +515,6 @@ func renameChannelCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
channel := getChannelFromChannelArg(a, args[0])
if channel == nil {
return errors.New("Unable to find channel '" + args[0] + "'")

Просмотреть файл

@@ -90,6 +90,18 @@ func createCommandCmdF(command *cobra.Command, args []string) error {
return errors.New("unable to find team '" + args[0] + "'")
}
// get the creator
creator, _ := command.Flags().GetString("creator")
user := getUserFromUserArg(a, creator)
if user == nil {
return errors.New("unable to find user '" + creator + "'")
}
// check if creator has permission to create slash commands
if !a.HasPermissionToTeam(user.Id, team.Id, model.PERMISSION_MANAGE_SLASH_COMMANDS) {
return errors.New("the creator must be a user who has permissions to manage slash commands")
}
title, _ := command.Flags().GetString("title")
description, _ := command.Flags().GetString("description")
trigger, _ := command.Flags().GetString("trigger-word")
@@ -102,11 +114,6 @@ func createCommandCmdF(command *cobra.Command, args []string) error {
}
url, _ := command.Flags().GetString("url")
creator, _ := command.Flags().GetString("creator")
user := getUserFromUserArg(a, creator)
if user == nil {
return errors.New("unable to find user '" + creator + "'")
}
responseUsername, _ := command.Flags().GetString("response-username")
icon, _ := command.Flags().GetString("icon")
autocomplete, _ := command.Flags().GetBool("autocomplete")

Просмотреть файл

@@ -19,6 +19,7 @@ func TestCreateCommand(t *testing.T) {
th.InitSystemAdmin()
defer th.TearDown()
team := th.BasicTeam
adminUser := th.TeamAdminUser
user := th.BasicUser
testCases := []struct {
@@ -28,17 +29,17 @@ func TestCreateCommand(t *testing.T) {
}{
{
"nil error",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"",
},
{
"Team not specified",
[]string{"command", "create", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: requires at least 1 arg(s), only received 0",
},
{
"Team not found",
[]string{"command", "create", "fakeTeam", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", "fakeTeam", "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: unable to find team",
},
{
@@ -51,54 +52,59 @@ func TestCreateCommand(t *testing.T) {
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", "fakeuser"},
"unable to find user",
},
{
"Creator not team admin",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
"the creator must be a user who has permissions to manage slash commands",
},
{
"Command not specified",
[]string{"command", "", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: unknown flag: --trigger-word",
},
{
"Trigger not specified",
[]string{"command", "create", team.Name, "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
`Error: required flag(s) "trigger-word" not set`,
},
{
"Blank trigger",
[]string{"command", "create", team.Name, "--trigger-word", "", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Invalid trigger",
},
{
"Trigger with space",
[]string{"command", "create", team.Name, "--trigger-word", "test cmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "test cmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: a trigger word must not contain spaces",
},
{
"Trigger starting with /",
[]string{"command", "create", team.Name, "--trigger-word", "/testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "/testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: a trigger word cannot begin with a /",
},
{
"URL not specified",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--creator", adminUser.Username},
`Error: required flag(s) "url" not set`,
},
{
"Blank URL",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "", "--creator", adminUser.Username},
"Invalid URL",
},
{
"Invalid URL",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "testcmd2", "--url", "localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Invalid URL",
},
{
"Duplicate Command",
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-word", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"This trigger word is already in use",
},
{
"Misspelled flag",
[]string{"command", "create", team.Name, "--trigger-wor", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", user.Username},
[]string{"command", "create", team.Name, "--trigger-wor", "testcmd", "--url", "http://localhost:8000/my-slash-handler", "--creator", adminUser.Username},
"Error: unknown flag:",
},
}

Просмотреть файл

@@ -56,19 +56,18 @@ type TestClientRequirements struct {
type TestNewConfig struct {
TestNewServiceSettings TestNewServiceSettings
TestNewTeamSettings TestNewTeamSettings
TestNewTeamSettings TestNewTeamSettings
}
type TestNewServiceSettings struct{
SiteUrl *string
UseLetsEncrypt *bool
TLSStrictTransportMaxAge *int64
AllowedThemes []string
type TestNewServiceSettings struct {
SiteUrl *string
UseLetsEncrypt *bool
TLSStrictTransportMaxAge *int64
AllowedThemes []string
}
type TestNewTeamSettings struct {
SiteName *string
SiteName *string
MaxUserPerTeam *int
}
@@ -469,16 +468,15 @@ func TestUpdateMap(t *testing.T) {
},
}
// create a map of type map[string]interface
configMap := configToMap(config)
cases := []struct{
Name string
cases := []struct {
Name string
configSettings []string
newVal []string
expected interface{}
} {
newVal []string
expected interface{}
}{
{
Name: "check for Map and string",
configSettings: []string{"TestNewServiceSettings", "SiteUrl"},
@@ -517,10 +515,9 @@ func TestUpdateMap(t *testing.T) {
},
}
for _, test := range cases {
t.Run(test.Name, func(t *testing.T){
t.Run(test.Name, func(t *testing.T) {
err := UpdateMap(configMap, test.configSettings, test.newVal)
if err != nil {

Просмотреть файл

@@ -44,12 +44,12 @@ func jobserverCmdF(command *cobra.Command, args []string) {
defer mlog.Info("Stopped Mattermost job server")
if !noJobs {
a.Jobs.StartWorkers()
defer a.Jobs.StopWorkers()
a.Srv.Jobs.StartWorkers()
defer a.Srv.Jobs.StopWorkers()
}
if !noSchedule {
a.Jobs.StartSchedulers()
defer a.Jobs.StopSchedulers()
a.Srv.Jobs.StartSchedulers()
defer a.Srv.Jobs.StopSchedulers()
}
signalChan := make(chan os.Signal, 1)

52
cmd/mattermost/commands/logs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package commands
import (
"github.com/mattermost/mattermost-server/mlog/human"
"github.com/spf13/cobra"
"io"
"os"
)
var LogsCmd = &cobra.Command{
Use: "logs",
Short: "Display logs in a human-readable format",
RunE: logsCmdF,
}
func init() {
LogsCmd.Flags().Bool("logrus", false, "Use logrus for formatting.")
RootCmd.AddCommand(LogsCmd)
}
func logsCmdF(command *cobra.Command, args []string) error {
// check stdin to see if we have a pipe
fi, err := os.Stdin.Stat()
if err != nil {
return err
}
var input io.Reader
if fi.Size() == 0 && fi.Mode()&os.ModeNamedPipe == 0 {
file, err := os.Open("mattermost.log")
if err != nil {
return err
}
defer file.Close()
input = file
} else {
input = os.Stdin
}
var writer human.LogWriter
if flag, _ := command.Flags().GetBool("logrus"); flag {
writer = human.NewLogrusWriter(os.Stdout)
} else {
writer = human.NewSimpleWriter(os.Stdout)
}
human.ProcessLogs(input, writer)
return nil
}

Просмотреть файл

@@ -147,19 +147,19 @@ func runServer(configFileLocation string, disableConfigWatch bool, usedPlatform
manualtesting.Init(api)
}
a.Go(func() {
a.Srv.Go(func() {
runSecurityJob(a)
})
a.Go(func() {
a.Srv.Go(func() {
runDiagnosticsJob(a)
})
a.Go(func() {
a.Srv.Go(func() {
runSessionCleanupJob(a)
})
a.Go(func() {
a.Srv.Go(func() {
runTokenCleanupJob(a)
})
a.Go(func() {
a.Srv.Go(func() {
runCommandWebhookCleanupJob(a)
})
@@ -181,12 +181,12 @@ func runServer(configFileLocation string, disableConfigWatch bool, usedPlatform
}
if *a.Config().JobSettings.RunJobs {
a.Jobs.StartWorkers()
defer a.Jobs.StopWorkers()
a.Srv.Jobs.StartWorkers()
defer a.Srv.Jobs.StopWorkers()
}
if *a.Config().JobSettings.RunScheduler {
a.Jobs.StartSchedulers()
defer a.Jobs.StopSchedulers()
a.Srv.Jobs.StartSchedulers()
defer a.Srv.Jobs.StopSchedulers()
}
notifyReady()

Просмотреть файл

@@ -32,6 +32,7 @@ var RemoveUsersCmd = &cobra.Command{
Short: "Remove users from team",
Long: "Remove some users from team",
Example: " team remove myteam user@example.com username",
Args: cobra.MinimumNArgs(2),
RunE: removeUsersCmdF,
}
@@ -40,6 +41,7 @@ var AddUsersCmd = &cobra.Command{
Short: "Add users to team",
Long: "Add some users to team",
Example: " team add myteam user@example.com username",
Args: cobra.MinimumNArgs(2),
RunE: addUsersCmdF,
}
@@ -49,6 +51,7 @@ var DeleteTeamsCmd = &cobra.Command{
Long: `Permanently delete some teams.
Permanently deletes a team along with all related information including posts from the database.`,
Example: " team delete myteam",
Args: cobra.MinimumNArgs(1),
RunE: deleteTeamsCmdF,
}
@@ -142,10 +145,6 @@ func removeUsersCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find team '" + args[0] + "'")
@@ -176,10 +175,6 @@ func addUsersCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 2 {
return errors.New("Not enough arguments.")
}
team := getTeamFromTeamArg(a, args[0])
if team == nil {
return errors.New("Unable to find team '" + args[0] + "'")
@@ -210,10 +205,6 @@ func deleteTeamsCmdF(command *cobra.Command, args []string) error {
}
defer a.Shutdown()
if len(args) < 1 {
return errors.New("Not enough arguments.")
}
confirmFlag, _ := command.Flags().GetBool("confirm")
if !confirmFlag {
var confirm string

Просмотреть файл

@@ -5,6 +5,7 @@ package commands
import (
"fmt"
"strings"
"github.com/mattermost/mattermost-server/model"
"github.com/pkg/errors"
@@ -40,6 +41,15 @@ var WebhookModifyIncomingCmd = &cobra.Command{
RunE: modifyIncomingWebhookCmdF,
}
var WebhookCreateOutgoingCmd = &cobra.Command{
Use: "create-outgoing",
Short: "Create outgoing webhook",
Long: "create outgoing webhook which allows external posting of messages from a specific channel",
Example: ` webhook create-outgoing --team myteam --user myusername --display-name mywebhook --trigger-words "build\ntest" --urls http://localhost:8000/my-webhook-handler
webhook create-outgoing --team myteam --channel mychannel --user myusername --display-name mywebhook --description "My cool webhook" --trigger-when 1 --trigger-words "build\ntest" --icon http://localhost:8000/my-slash-handler-bot-icon.png --urls http://localhost:8000/my-webhook-handler --content-type "application/json"`,
RunE: createOutgoingWebhookCmdF,
}
func listWebhookCmdF(command *cobra.Command, args []string) error {
app, err := InitDBCommandContextCobra(command)
if err != nil {
@@ -177,6 +187,81 @@ func modifyIncomingWebhookCmdF(command *cobra.Command, args []string) error {
return nil
}
func createOutgoingWebhookCmdF(command *cobra.Command, args []string) error {
app, err := InitDBCommandContextCobra(command)
if err != nil {
return err
}
defer app.Shutdown()
teamArg, errTeam := command.Flags().GetString("team")
if errTeam != nil || teamArg == "" {
return errors.New("Team is required")
}
team := getTeamFromTeamArg(app, teamArg)
if team == nil {
return errors.New("Unable to find team: " + teamArg)
}
userArg, errUser := command.Flags().GetString("user")
if errUser != nil || userArg == "" {
return errors.New("User is required")
}
user := getUserFromUserArg(app, userArg)
if user == nil {
return errors.New("Unable to find user: " + userArg)
}
displayName, errName := command.Flags().GetString("display-name")
if errName != nil || displayName == "" {
return errors.New("Display name is required")
}
triggerWordsString, errWords := command.Flags().GetString("trigger-words")
if errWords != nil || triggerWordsString == "" {
return errors.New("Trigger word or words required")
}
triggerWords := strings.Split(triggerWordsString, "\n")
callbackURLsString, errURL := command.Flags().GetString("urls")
if errURL != nil || callbackURLsString == "" {
return errors.New("Callback URL or URLs required")
}
callbackURLs := strings.Split(callbackURLsString, "\n")
triggerWhen, _ := command.Flags().GetInt("trigger-when")
description, _ := command.Flags().GetString("description")
contentType, _ := command.Flags().GetString("content-type")
iconURL, _ := command.Flags().GetString("icon")
outgoingWebhook := &model.OutgoingWebhook{
CreatorId: user.Id,
Username: user.Username,
TeamId: team.Id,
TriggerWords: triggerWords,
TriggerWhen: triggerWhen,
CallbackURLs: callbackURLs,
DisplayName: displayName,
Description: description,
ContentType: contentType,
IconURL: iconURL,
}
channelArg, _ := command.Flags().GetString("channel")
if channelArg != "" {
channel := getChannelFromChannelArg(app, channelArg)
if channel != nil {
outgoingWebhook.ChannelId = channel.Id
}
}
if _, err := app.CreateOutgoingWebhook(outgoingWebhook); err != nil {
return err
}
return nil
}
func init() {
WebhookCreateIncomingCmd.Flags().String("channel", "", "Channel ID")
WebhookCreateIncomingCmd.Flags().String("user", "", "User ID")
@@ -191,10 +276,22 @@ func init() {
WebhookModifyIncomingCmd.Flags().String("icon", "", "Icon URL")
WebhookModifyIncomingCmd.Flags().Bool("lock-to-channel", false, "Lock to channel")
WebhookCreateOutgoingCmd.Flags().String("team", "", "Team name or ID (required)")
WebhookCreateOutgoingCmd.Flags().String("channel", "", "Channel name or ID")
WebhookCreateOutgoingCmd.Flags().String("user", "", "User username, email, or ID (required)")
WebhookCreateOutgoingCmd.Flags().String("display-name", "", "Outgoing webhook display name (required)")
WebhookCreateOutgoingCmd.Flags().String("description", "", "Outgoing webhook description")
WebhookCreateOutgoingCmd.Flags().String("trigger-words", "", "Words to trigger webhook (word1\nword2) (required)")
WebhookCreateOutgoingCmd.Flags().Int("trigger-when", 0, "When to trigger webhook (either when trigger word is first (enter 1) or when it's anywhere (enter 0))")
WebhookCreateOutgoingCmd.Flags().String("icon", "", "Icon URL")
WebhookCreateOutgoingCmd.Flags().String("urls", "", "Callback URLs (url1\nurl2) (required)")
WebhookCreateOutgoingCmd.Flags().String("content-type", "", "Content-type")
WebhookCmd.AddCommand(
WebhookListCmd,
WebhookCreateIncomingCmd,
WebhookModifyIncomingCmd,
WebhookCreateOutgoingCmd,
)
RootCmd.AddCommand(WebhookCmd)

Просмотреть файл

@@ -4,11 +4,12 @@
package commands
import (
"github.com/stretchr/testify/require"
"strconv"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost-server/api4"
"github.com/mattermost/mattermost-server/model"
)
@@ -116,9 +117,9 @@ func TestModifyIncomingWebhook(t *testing.T) {
displayName := "myhookincname"
incomingWebhook := &model.IncomingWebhook{
ChannelId: th.BasicChannel.Id,
DisplayName: displayName,
Description: description,
ChannelId: th.BasicChannel.Id,
DisplayName: displayName,
Description: description,
}
oldHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, incomingWebhook)
@@ -150,3 +151,62 @@ func TestModifyIncomingWebhook(t *testing.T) {
t.Fatal("Failed to update incoming webhook")
}
}
func TestCreateOutgoingWebhook(t *testing.T) {
th := api4.Setup().InitBasic().InitSystemAdmin()
defer th.TearDown()
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true })
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostIconOverride = true })
defaultRolePermissions := th.SaveDefaultRolePermissions()
defer func() {
th.RestoreDefaultRolePermissions(defaultRolePermissions)
}()
th.AddPermissionToRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_ADMIN_ROLE_ID)
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID)
// team, user, display name, trigger words, callback urls are required
team := th.BasicTeam.Id
user := th.BasicUser.Id
displayName := "totally radical webhook"
triggerWords := "build\ndefenestrate"
callbackURLs := "http://localhost:8000/my-webhook-handler\nhttp://localhost:8000/my-webhook-handler2"
// should fail because team is not specified
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user))
// should fail because user is not specified
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs))
// should fail because display name is not specified
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user))
// should fail because trigger words are not specified
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--urls", callbackURLs, "--user", user))
// should fail because callback URLs are not specified
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-words", triggerWords, "--user", user))
// should fail because outgoing webhooks cannot be made for private channels
require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicPrivateChannel.Id, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user))
CheckCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicChannel.Id, "--display-name", displayName, "--trigger-words", triggerWords, "--urls", callbackURLs, "--user", user)
webhooks, err := th.App.GetOutgoingWebhooksPage(0, 1000)
if err != nil {
t.Fatal("Unable to retreive outgoing webhooks")
}
found := false
for _, webhook := range webhooks {
if webhook.DisplayName == displayName && webhook.CreatorId == th.BasicUser.Id {
found = true
}
}
if !found {
t.Fatal("Failed to create incoming webhook")
}
}

Просмотреть файл

@@ -330,7 +330,7 @@
"NicknameAttribute": "",
"LocaleAttribute": "",
"PositionAttribute": "",
"LoginButtonText": "With SAML",
"LoginButtonText": "SAML",
"LoginButtonColor": "",
"LoginButtonBorderColor": "",
"LoginButtonTextColor": ""

Просмотреть файл

@@ -694,10 +694,6 @@
"id": "api.command_leave.name",
"translation": "leave"
},
{
"id": "api.command_leave.success",
"translation": "Left the channel."
},
{
"id": "api.command_logout.desc",
"translation": "Logout of Mattermost"
@@ -3758,6 +3754,10 @@
"id": "model.channel_member.is_valid.email_value.app_error",
"translation": "Invalid email notification value"
},
{
"id": "model.channel_member.is_valid.ignore_channel_mentions_value.app_error",
"translation": "Invalid ignore channel mentions status"
},
{
"id": "model.channel_member.is_valid.notify_level.app_error",
"translation": "Invalid notify level"
@@ -4802,6 +4802,18 @@
"id": "model.terms_of_service.is_valid.text.app_error",
"translation": "Custom terms of service text is too long. Maximum {{.MaxLength}} characters allowed."
},
{
"id": "model.user_terms_of_service.is_valid.user_id.app_error",
"translation": "Missing required user terms of service property: user_id."
},
{
"id": "model.user_terms_of_service.is_valid.service_terms_id.app_error",
"translation": "Missing required user terms of service property: service_terms_id."
},
{
"id": "model.user_terms_of_service.is_valid.create_at.app_error",
"translation": "Missing required user terms of service property: create_at."
},
{
"id": "oauth.gitlab.tos.error",
"translation": "GitLab's Terms of Service have updated. Please go to gitlab.com to accept them and then try logging into Mattermost again."
@@ -6462,6 +6474,22 @@
"id": "store.sql_terms_of_service_store.get.no_rows.app_error",
"translation": "No terms of service found."
},
{
"id": "store.sql_user_terms_of_service.get_by_user.no_rows.app_error",
"translation": "No user terms of service found."
},
{
"id": "store.sql_user_terms_of_service.get_by_user.app_error",
"translation": "Unable to fetch user terms of service."
},
{
"id": "store.sql_user_terms_of_service.save.app_error",
"translation": "Unable to save user terms of service."
},
{
"id": "store.sql_user_terms_of_service.delete.app_error",
"translation": "Unable to delete user terms of service."
},
{
"id": "system.message.name",
"translation": "System"

Просмотреть файл

@@ -61,7 +61,7 @@ func (scheduler *Scheduler) ScheduleJob(cfg *model.Config, pendingJobs bool, las
// Check the migration job isn't wedged.
if job != nil && job.LastActivityAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS && job.CreateAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS {
mlog.Warn("Job appears to be wedged. Rescheduling another instance.", mlog.String("scheduler", scheduler.Name()), mlog.String("wedged_job_id", job.Id), mlog.String("migration_key", key))
if err := scheduler.App.Jobs.SetJobError(job, nil); err != nil {
if err := scheduler.App.Srv.Jobs.SetJobError(job, nil); err != nil {
mlog.Error("Worker: Failed to set job error", mlog.String("scheduler", scheduler.Name()), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
}
return scheduler.createJob(key, job, scheduler.App.Srv.Store)
@@ -102,7 +102,7 @@ func (scheduler *Scheduler) createJob(migrationKey string, lastJob *model.Job, s
JOB_DATA_KEY_MIGRATION_LAST_DONE: lastDone,
}
if job, err := scheduler.App.Jobs.CreateJob(model.JOB_TYPE_MIGRATIONS, data); err != nil {
if job, err := scheduler.App.Srv.Jobs.CreateJob(model.JOB_TYPE_MIGRATIONS, data); err != nil {
return nil, err
} else {
return job, nil

Просмотреть файл

@@ -33,7 +33,7 @@ func (m *MigrationsJobInterfaceImpl) MakeWorker() model.Worker {
stop: make(chan bool, 1),
stopped: make(chan bool, 1),
jobs: make(chan model.Job),
jobServer: m.App.Jobs,
jobServer: m.App.Srv.Jobs,
app: m.App,
}
@@ -83,7 +83,7 @@ func (worker *Worker) DoJob(job *model.Job) {
cancelCtx, cancelCancelWatcher := context.WithCancel(context.Background())
cancelWatcherChan := make(chan interface{}, 1)
go worker.app.Jobs.CancellationWatcher(cancelCtx, job.Id, cancelWatcherChan)
go worker.app.Srv.Jobs.CancellationWatcher(cancelCtx, job.Id, cancelWatcherChan)
defer cancelCancelWatcher()
@@ -111,7 +111,7 @@ func (worker *Worker) DoJob(job *model.Job) {
return
} else {
job.Data[JOB_DATA_KEY_MIGRATION_LAST_DONE] = progress
if err := worker.app.Jobs.UpdateInProgressJobData(job); err != nil {
if err := worker.app.Srv.Jobs.UpdateInProgressJobData(job); err != nil {
mlog.Error("Worker: Failed to update migration status data for job", mlog.String("worker", worker.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
worker.setJobError(job, err)
return
@@ -122,20 +122,20 @@ func (worker *Worker) DoJob(job *model.Job) {
}
func (worker *Worker) setJobSuccess(job *model.Job) {
if err := worker.app.Jobs.SetJobSuccess(job); err != nil {
if err := worker.app.Srv.Jobs.SetJobSuccess(job); err != nil {
mlog.Error("Worker: Failed to set success for job", mlog.String("worker", worker.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
worker.setJobError(job, err)
}
}
func (worker *Worker) setJobError(job *model.Job, appError *model.AppError) {
if err := worker.app.Jobs.SetJobError(job, appError); err != nil {
if err := worker.app.Srv.Jobs.SetJobError(job, appError); err != nil {
mlog.Error("Worker: Failed to set job error", mlog.String("worker", worker.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
}
}
func (worker *Worker) setJobCanceled(job *model.Job) {
if err := worker.app.Jobs.SetJobCanceled(job); err != nil {
if err := worker.app.Srv.Jobs.SetJobCanceled(job); err != nil {
mlog.Error("Worker: Failed to mark job as canceled", mlog.String("worker", worker.name), mlog.String("job_id", job.Id), mlog.String("error", err.Error()))
}
}

51
mlog/human/entry.go Обычный файл
Просмотреть файл

@@ -0,0 +1,51 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package human
import (
"fmt"
"github.com/mattermost/mattermost-server/mlog"
"strings"
"time"
)
type LogEntry struct {
Time time.Time
Level string
Message string
Caller string
Fields []mlog.Field
}
// Provide default string representation. Used by SimpleWriter
func (f LogEntry) String() string {
var sb strings.Builder
if !f.Time.IsZero() {
sb.WriteString(f.Time.Format(time.RFC3339Nano))
sb.WriteRune(' ')
}
if f.Level != "" {
sb.WriteString(f.Level)
sb.WriteRune(' ')
}
if f.Caller != "" {
sb.WriteString(f.Caller)
sb.WriteRune(' ')
}
for _, field := range f.Fields {
sb.WriteString(field.Key)
sb.WriteRune('=')
sb.WriteString(fmt.Sprint(field.Interface))
sb.WriteRune(' ')
}
if f.Message != "" {
// If the message is multiple lines, start the whole message on a new line
if strings.ContainsRune(f.Message, '\n') {
sb.WriteRune('\n')
}
sb.WriteString(f.Message)
}
return sb.String()
}

76
mlog/human/logrus_writer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package human
import (
"fmt"
"github.com/sirupsen/logrus"
"io"
"time"
)
type LogrusWriter struct {
logger *logrus.Logger
}
func (w *LogrusWriter) Write(e LogEntry) {
if e.Level == "" {
fmt.Fprintln(w.logger.Out, e.Message)
return
}
lvl, err := logrus.ParseLevel(e.Level)
if err != nil {
fmt.Fprintln(w.logger.Out, err)
lvl = logrus.TraceLevel + 1 // will invoke Println
}
logger := w.logger.WithTime(e.Time)
if e.Caller != "" {
// logrus has a system of reporting the caller, but there's no easy way to override it
logger = logger.WithField("caller", e.Caller)
}
for _, field := range e.Fields {
logger = logger.WithField(field.Key, field.Interface)
}
switch lvl {
case logrus.PanicLevel:
// Prevent panic from causing us to exit
defer func() {
recover()
}()
logger.Panic(e.Message)
case logrus.FatalLevel:
logger.Fatal(e.Message)
case logrus.ErrorLevel:
logger.Error(e.Message)
case logrus.WarnLevel:
logger.Warn(e.Message)
case logrus.InfoLevel:
logger.Info(e.Message)
case logrus.DebugLevel:
logger.Debug(e.Message)
case logrus.TraceLevel:
logger.Trace(e.Message)
default:
logger.Println(e.Message)
}
}
func NewLogrusWriter(output io.Writer) *LogrusWriter {
w := new(LogrusWriter)
w.logger = logrus.New()
w.logger.SetLevel(logrus.TraceLevel) // don't filter any logs
w.logger.ExitFunc = func(int) {} // prevent Fatal from causing us to exit
w.logger.SetReportCaller(false)
w.logger.SetOutput(output)
var tf logrus.TextFormatter
tf.FullTimestamp = true
tf.TimestampFormat = time.RFC3339Nano
w.logger.SetFormatter(&tf)
return w
}

180
mlog/human/parser.go Обычный файл
Просмотреть файл

@@ -0,0 +1,180 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package human
import (
"encoding/json"
"errors"
"fmt"
"github.com/mattermost/mattermost-server/mlog"
"io"
"strconv"
"strings"
"time"
)
func ParseLogMessage(msg string) LogEntry {
result, err := parseLogMessage(msg)
if err != nil {
// If failed to parse, just output a LogEntry where all fields are blank, but Message is the original string
var result2 LogEntry
result2.Message = msg
return result2
}
return result
}
func parseLogMessage(msg string) (result LogEntry, err error) {
// Note: This implementation uses a custom json decoding loop.
// The primary advantage of this versus decoding directly into a map is to
// preserve the order of the fields. This can be simplified if we end up
// having the formatter sort fields alphabetically (logrus does by default)
dec := json.NewDecoder(strings.NewReader(msg))
// look for an initial "{"
if token, err := dec.Token(); err != nil {
return result, err
} else {
d, ok := token.(json.Delim)
if !ok || d != '{' {
return result, errors.New(fmt.Sprintf("input is not a JSON object, found: %v", token))
}
}
// read all key-value pairs
for dec.More() {
key, err := dec.Token()
if err != nil {
return result, err
}
if skey, ok := key.(string); !ok {
return result, errors.New("key is not a value string")
} else {
if !dec.More() {
return result, errors.New("missing value pair")
}
switch skey {
case "ts":
var ts json.Number
if err := dec.Decode(&ts); err != nil {
return result, err
}
if time, err := numberToTime(ts); err != nil {
return result, err
} else {
result.Time = time
}
case "level":
if s, err := decodeAsString(dec); err != nil {
return result, err
} else {
result.Level = s
}
case "msg":
if s, err := decodeAsString(dec); err != nil {
return result, err
} else {
result.Message = s
}
case "caller":
if s, err := decodeAsString(dec); err != nil {
return result, err
} else {
result.Caller = s
}
default:
var p interface{}
if err := dec.Decode(&p); err != nil {
return result, err
}
var f mlog.Field
f.Key = skey
f.Interface = p
result.Fields = append(result.Fields, f)
}
}
}
// read the "}"
if token, err := dec.Token(); err != nil {
return result, err
} else {
d, ok := token.(json.Delim)
if !ok || d != '}' {
return result, errors.New(fmt.Sprintf("failed to read '}', read: %v", token))
}
}
// make sure nothing else trailing
if token, err := dec.Token(); err != io.EOF {
return result, err
} else if token != nil {
return result, errors.New("found trailing data")
}
return result, nil
}
// Translate a number into a time
func numberToTime(v json.Number) (time.Time, error) {
// Using floating point math to extract the nanoseconds leads to a time that doesn't exactly match the input
// Instead, parse out the components from the string representation
var t time.Time
// First make sure it is a number...
flt, err := v.Float64()
if err != nil {
return t, err
}
s := v.String()
if strings.ContainsAny(s, "eE") {
// input is in scientific notation. Convert to standard decimal notation
s = strconv.FormatFloat(flt, 'f', -1, 64)
}
// extract the seconds and nanoseconds separately
var nanos, sec int64
parts := strings.SplitN(s, ".", 2)
sec, err = strconv.ParseInt(parts[0], 10, 64)
if err != nil {
return t, err
}
if len(parts) == 2 {
nanosText := parts[1] + "000000000"
nanosText = nanosText[:9]
nanos, err = strconv.ParseInt(nanosText, 10, 64)
if err != nil {
return t, err
}
}
t = time.Unix(sec, nanos)
return t, nil
}
// Decodes a value from JSON, coercing it to a string value as necessary
func decodeAsString(dec *json.Decoder) (s string, err error) {
var v interface{}
if err = dec.Decode(&v); err != nil {
return s, err
}
var ok bool
if s, ok = v.(string); ok {
return s, err
}
s = fmt.Sprint(v)
return s, err
}

23
mlog/human/process.go Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package human
import (
"bufio"
"io"
)
type LogWriter interface {
Write(e LogEntry)
}
// Read JSON logs from input and write formatted logs to the output
func ProcessLogs(reader io.Reader, writer LogWriter) {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
s := scanner.Text()
e := ParseLogMessage(s)
writer.Write(e)
}
}

23
mlog/human/simple_writer.go Обычный файл
Просмотреть файл

@@ -0,0 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package human
import (
"fmt"
"io"
)
type SimpleWriter struct {
out io.Writer
}
func (w *SimpleWriter) Write(e LogEntry) {
fmt.Fprintln(w.out, e)
}
func NewSimpleWriter(out io.Writer) *SimpleWriter {
w := new(SimpleWriter)
w.out = out
return w
}

Просмотреть файл

@@ -11,12 +11,16 @@ import (
)
const (
CHANNEL_NOTIFY_DEFAULT = "default"
CHANNEL_NOTIFY_ALL = "all"
CHANNEL_NOTIFY_MENTION = "mention"
CHANNEL_NOTIFY_NONE = "none"
CHANNEL_MARK_UNREAD_ALL = "all"
CHANNEL_MARK_UNREAD_MENTION = "mention"
CHANNEL_NOTIFY_DEFAULT = "default"
CHANNEL_NOTIFY_ALL = "all"
CHANNEL_NOTIFY_MENTION = "mention"
CHANNEL_NOTIFY_NONE = "none"
CHANNEL_MARK_UNREAD_ALL = "all"
CHANNEL_MARK_UNREAD_MENTION = "mention"
IGNORE_CHANNEL_MENTIONS_DEFAULT = "default"
IGNORE_CHANNEL_MENTIONS_OFF = "off"
IGNORE_CHANNEL_MENTIONS_ON = "on"
IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP = "ignore_channel_mentions"
)
type ChannelUnread struct {
@@ -116,6 +120,12 @@ func (o *ChannelMember) IsValid() *AppError {
}
}
if ignoreChannelMentions, ok := o.NotifyProps[IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP]; ok {
if len(ignoreChannelMentions) > 40 || !IsIgnoreChannelMentionsValid(ignoreChannelMentions) {
return NewAppError("ChannelMember.IsValid", "model.channel_member.is_valid.ignore_channel_mentions_value.app_error", nil, "ignore_channel_mentions="+ignoreChannelMentions, http.StatusBadRequest)
}
}
return nil
}
@@ -146,11 +156,16 @@ func IsSendEmailValid(sendEmail string) bool {
return sendEmail == CHANNEL_NOTIFY_DEFAULT || sendEmail == "true" || sendEmail == "false"
}
func IsIgnoreChannelMentionsValid(ignoreChannelMentions string) bool {
return ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_ON || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_OFF || ignoreChannelMentions == IGNORE_CHANNEL_MENTIONS_DEFAULT
}
func GetDefaultChannelNotifyProps() StringMap {
return StringMap{
DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL,
PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
DESKTOP_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
MARK_UNREAD_NOTIFY_PROP: CHANNEL_MARK_UNREAD_ALL,
PUSH_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
EMAIL_NOTIFY_PROP: CHANNEL_NOTIFY_DEFAULT,
IGNORE_CHANNEL_MENTIONS_NOTIFY_PROP: IGNORE_CHANNEL_MENTIONS_DEFAULT,
}
}

Просмотреть файл

@@ -401,7 +401,7 @@ func (c *Client4) GetRedirectLocationRoute() string {
return fmt.Sprintf("/redirect_location")
}
func (c *Client4) GetRegisterTermsOfServiceRoute(userId string) string {
func (c *Client4) GetUserTermsOfServiceRoute(userId string) string {
return c.GetUserRoute(userId) + "/terms_of_service"
}
@@ -3771,7 +3771,7 @@ func (c *Client4) GetPluginStatuses() (PluginStatuses, *Response) {
}
}
// RemovePlugin will deactivate and delete a plugin.
// RemovePlugin will disable and delete a plugin.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) RemovePlugin(id string) (bool, *Response) {
if r, err := c.DoApiDelete(c.GetPluginRoute(id)); err != nil {
@@ -3793,7 +3793,7 @@ func (c *Client4) GetWebappPlugins() ([]*Manifest, *Response) {
}
}
// ActivatePlugin will activate an plugin installed.
// EnablePlugin will enable an plugin installed.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) EnablePlugin(id string) (bool, *Response) {
if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/enable", ""); err != nil {
@@ -3804,7 +3804,7 @@ func (c *Client4) EnablePlugin(id string) (bool, *Response) {
}
}
// DeactivatePlugin will deactivate an active plugin.
// DisablePlugin will disable an enabled plugin.
// WARNING: PLUGINS ARE STILL EXPERIMENTAL. THIS FUNCTION IS SUBJECT TO CHANGE.
func (c *Client4) DisablePlugin(id string) (bool, *Response) {
if r, err := c.DoApiPost(c.GetPluginRoute(id)+"/disable", ""); err != nil {
@@ -3849,7 +3849,7 @@ func (c *Client4) GetRedirectLocation(urlParam, etag string) (string, *Response)
}
func (c *Client4) RegisteTermsOfServiceAction(userId, termsOfServiceId string, accepted bool) (*bool, *Response) {
url := c.GetRegisterTermsOfServiceRoute(userId)
url := c.GetUserTermsOfServiceRoute(userId)
data := map[string]interface{}{"termsOfServiceId": termsOfServiceId, "accepted": accepted}
if r, err := c.DoApiPost(url, StringInterfaceToJson(data)); err != nil {
@@ -3871,11 +3871,22 @@ func (c *Client4) GetTermsOfService(etag string) (*TermsOfService, *Response) {
}
}
func (c *Client4) GetUserTermsOfService(userId, etag string) (*UserTermsOfService, *Response) {
url := c.GetUserTermsOfServiceRoute(userId)
if r, err := c.DoApiGet(url, etag); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)
return UserTermsOfServiceFromJson(r.Body), BuildResponse(r)
}
}
func (c *Client4) CreateTermsOfService(text, userId string) (*TermsOfService, *Response) {
url := c.GetTermsOfServiceRoute()
data := map[string]string{"text": text}
if r, err := c.DoApiPost(url, MapToJson(data)); err != nil {
data := map[string]interface{}{"text": text}
if r, err := c.DoApiPost(url, StringInterfaceToJson(data)); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
defer closeBody(r)

Просмотреть файл

@@ -112,6 +112,7 @@ const (
SUPPORT_SETTINGS_DEFAULT_HELP_LINK = "https://about.mattermost.com/default-help/"
SUPPORT_SETTINGS_DEFAULT_REPORT_A_PROBLEM_LINK = "https://about.mattermost.com/default-report-a-problem/"
SUPPORT_SETTINGS_DEFAULT_SUPPORT_EMAIL = "feedback@mattermost.com"
SUPPORT_SETTINGS_DEFAULT_RE_ACCEPTANCE_PERIOD = 365
LDAP_SETTINGS_DEFAULT_FIRST_NAME_ATTRIBUTE = ""
LDAP_SETTINGS_DEFAULT_LAST_NAME_ATTRIBUTE = ""
@@ -1030,13 +1031,14 @@ type PrivacySettings struct {
}
type SupportSettings struct {
TermsOfServiceLink *string
PrivacyPolicyLink *string
AboutLink *string
HelpLink *string
ReportAProblemLink *string
SupportEmail *string
CustomTermsOfServiceEnabled *bool
TermsOfServiceLink *string
PrivacyPolicyLink *string
AboutLink *string
HelpLink *string
ReportAProblemLink *string
SupportEmail *string
CustomTermsOfServiceEnabled *bool
CustomTermsOfServiceReAcceptancePeriod *int
}
func (s *SupportSettings) SetDefaults() {
@@ -1087,6 +1089,10 @@ func (s *SupportSettings) SetDefaults() {
if s.CustomTermsOfServiceEnabled == nil {
s.CustomTermsOfServiceEnabled = NewBool(false)
}
if s.CustomTermsOfServiceReAcceptancePeriod == nil {
s.CustomTermsOfServiceReAcceptancePeriod = NewInt(SUPPORT_SETTINGS_DEFAULT_RE_ACCEPTANCE_PERIOD)
}
}
type AnnouncementSettings struct {

Просмотреть файл

@@ -85,7 +85,7 @@ func (o *FileInfo) IsValid() *AppError {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.id.app_error", nil, "", http.StatusBadRequest)
}
if len(o.CreatorId) != 26 {
if len(o.CreatorId) != 26 && o.CreatorId != "nouser" {
return NewAppError("FileInfo.IsValid", "model.file_info.is_valid.user_id.app_error", nil, "id="+o.Id, http.StatusBadRequest)
}

Просмотреть файл

@@ -5,6 +5,7 @@ package model
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -12,6 +13,7 @@ import (
"path/filepath"
"strings"
"github.com/blang/semver"
"gopkg.in/yaml.v2"
)
@@ -111,6 +113,11 @@ type Manifest struct {
// A version number for your plugin. Semantic versioning is recommended: http://semver.org
Version string `json:"version" yaml:"version"`
// The minimum Mattermost server version required for your plugin.
//
// Minimum server version: 5.6
MinServerVersion string `json:"min_server_version,omitempty" yaml:"min_server_version,omitempty"`
// Server defines the server-side portion of your plugin.
Server *ManifestServer `json:"server,omitempty" yaml:"server,omitempty"`
@@ -242,6 +249,18 @@ func (m *Manifest) HasWebapp() bool {
return m.Webapp != nil
}
func (m *Manifest) MeetMinServerVersion(serverVersion string) (bool, error) {
minServerVersion, err := semver.Parse(m.MinServerVersion)
if err != nil {
return false, errors.New("failed to parse MinServerVersion")
}
sv := semver.MustParse(serverVersion)
if sv.LT(minServerVersion) {
return false, nil
}
return true, nil
}
// FindManifest will find and parse the manifest in a given directory.
//
// In all cases other than a does-not-exist error, path is set to the path of the manifest file that was
@@ -254,25 +273,23 @@ func FindManifest(dir string) (manifest *Manifest, path string, err error) {
f, ferr := os.Open(path)
if ferr != nil {
if !os.IsNotExist(ferr) {
err = ferr
return
return nil, "", ferr
}
continue
}
b, ioerr := ioutil.ReadAll(f)
f.Close()
if ioerr != nil {
err = ioerr
return
return nil, path, ioerr
}
var parsed Manifest
err = yaml.Unmarshal(b, &parsed)
if err != nil {
return
return nil, path, err
}
manifest = &parsed
manifest.Id = strings.ToLower(manifest.Id)
return
return manifest, path, nil
}
path = filepath.Join(dir, "plugin.json")
@@ -281,16 +298,15 @@ func FindManifest(dir string) (manifest *Manifest, path string, err error) {
if os.IsNotExist(ferr) {
path = ""
}
err = ferr
return
return nil, path, ferr
}
defer f.Close()
var parsed Manifest
err = json.NewDecoder(f).Decode(&parsed)
if err != nil {
return
return nil, path, err
}
manifest = &parsed
manifest.Id = strings.ToLower(manifest.Id)
return
return manifest, path, nil
}

Просмотреть файл

@@ -63,7 +63,8 @@ func TestFindManifest(t *testing.T) {
func TestManifestUnmarshal(t *testing.T) {
expected := Manifest{
Id: "theid",
Id: "theid",
MinServerVersion: "5.6.0",
Server: &ManifestServer{
Executable: "theexecutable",
Executables: &ManifestExecutables{
@@ -101,6 +102,7 @@ func TestManifestUnmarshal(t *testing.T) {
var yamlResult Manifest
require.NoError(t, yaml.Unmarshal([]byte(`
id: theid
min_server_version: 5.6.0
server:
executable: theexecutable
executables:
@@ -129,6 +131,7 @@ settings_schema:
var jsonResult Manifest
require.NoError(t, json.Unmarshal([]byte(`{
"id": "theid",
"min_server_version": "5.6.0",
"server": {
"executable": "theexecutable",
"executables": {
@@ -182,6 +185,27 @@ func TestFindManifest_FileErrors(t *testing.T) {
}
}
func TestFindManifest_FolderPermission(t *testing.T) {
for _, tc := range []string{"plugin.yaml", "plugin.json"} {
dir, err := ioutil.TempDir("", "mm-plugin-test")
defer os.RemoveAll(dir)
path := filepath.Join(dir, tc)
require.NoError(t, os.Mkdir(path, 0700))
//User does not have permission in the plugin folder
err = os.Chmod(dir, 0066)
require.NoError(t, err)
m, mpath, err := FindManifest(dir)
assert.Nil(t, m)
assert.Equal(t, "", mpath)
assert.Error(t, err, tc)
assert.False(t, os.IsNotExist(err), tc)
}
}
func TestManifestJson(t *testing.T) {
manifest := &Manifest{
Id: "theid",
@@ -246,10 +270,11 @@ func TestManifestHasClient(t *testing.T) {
func TestManifestClientManifest(t *testing.T) {
manifest := &Manifest{
Id: "theid",
Name: "thename",
Description: "thedescription",
Version: "0.0.1",
Id: "theid",
Name: "thename",
Description: "thedescription",
Version: "0.0.1",
MinServerVersion: "5.6.0",
Server: &ManifestServer{
Executable: "theexecutable",
},
@@ -284,6 +309,7 @@ func TestManifestClientManifest(t *testing.T) {
assert.Equal(t, manifest.Id, sanitized.Id)
assert.Equal(t, manifest.Version, sanitized.Version)
assert.Equal(t, manifest.MinServerVersion, sanitized.MinServerVersion)
assert.Equal(t, "/static/theid/theid_000102030405060708090a0b0c0d0e0f_bundle.js", sanitized.Webapp.BundlePath)
assert.Equal(t, manifest.Webapp.BundleHash, sanitized.Webapp.BundleHash)
assert.Equal(t, manifest.SettingsSchema, sanitized.SettingsSchema)
@@ -293,6 +319,7 @@ func TestManifestClientManifest(t *testing.T) {
assert.NotEmpty(t, manifest.Id)
assert.NotEmpty(t, manifest.Version)
assert.NotEmpty(t, manifest.MinServerVersion)
assert.NotEmpty(t, manifest.Webapp)
assert.NotEmpty(t, manifest.Name)
assert.NotEmpty(t, manifest.Description)
@@ -594,3 +621,53 @@ func TestManifestHasWebapp(t *testing.T) {
})
}
}
func TestManifestMeetMinServerVersion(t *testing.T) {
for name, test := range map[string]struct {
MinServerVersion string
ServerVersion string
ShouldError bool
ShouldFulfill bool
}{
"generously fulfilled": {
MinServerVersion: "5.5.0",
ServerVersion: "5.6.0",
ShouldError: false,
ShouldFulfill: true,
},
"exactly fulfilled": {
MinServerVersion: "5.6.0",
ServerVersion: "5.6.0",
ShouldError: false,
ShouldFulfill: true,
},
"not fulfilled": {
MinServerVersion: "5.6.0",
ServerVersion: "5.5.0",
ShouldError: false,
ShouldFulfill: false,
},
"fail to parse MinServerVersion": {
MinServerVersion: "abc",
ServerVersion: "5.5.0",
ShouldError: true,
},
} {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
manifest := Manifest{
MinServerVersion: test.MinServerVersion,
}
fulfilled, err := manifest.MeetMinServerVersion(test.ServerVersion)
if test.ShouldError {
assert.NotNil(err)
assert.False(fulfilled)
return
}
assert.Nil(err)
assert.Equal(test.ShouldFulfill, fulfilled)
})
}
}

Просмотреть файл

@@ -10,7 +10,7 @@ import (
const (
USER_AUTH_SERVICE_SAML = "saml"
USER_AUTH_SERVICE_SAML_TEXT = "With SAML"
USER_AUTH_SERVICE_SAML_TEXT = "SAML"
)
type SamlAuthRequest struct {

Просмотреть файл

@@ -11,7 +11,6 @@ import (
"unicode/utf8"
)
// we only ever need the latest version of terms of service
const TERMS_OF_SERVICE_CACHE_SIZE = 1
type TermsOfService struct {
@@ -58,7 +57,7 @@ func InvalidTermsOfServiceError(fieldName string, termsOfServiceId string) *AppE
if termsOfServiceId != "" {
details = "terms_of_service_id=" + termsOfServiceId
}
return NewAppError("TermsOfServiceStore.IsValid", id, map[string]interface{}{"MaxLength": POST_MESSAGE_MAX_RUNES_V2}, details, http.StatusBadRequest)
return NewAppError("TermsOfService.IsValid", id, map[string]interface{}{"MaxLength": POST_MESSAGE_MAX_RUNES_V2}, details, http.StatusBadRequest)
}
func (t *TermsOfService) PreSave() {

Просмотреть файл

@@ -4,9 +4,10 @@
package model
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestTermsOfServiceIsValid(t *testing.T) {

Просмотреть файл

@@ -50,33 +50,32 @@ const (
)
type User struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
DeleteAt int64 `json:"delete_at"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
AuthData *string `json:"auth_data,omitempty"`
AuthService string `json:"auth_service"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified,omitempty"`
Nickname string `json:"nickname"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Position string `json:"position"`
Roles string `json:"roles"`
AllowMarketing bool `json:"allow_marketing,omitempty"`
Props StringMap `json:"props,omitempty"`
NotifyProps StringMap `json:"notify_props,omitempty"`
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
FailedAttempts int `json:"failed_attempts,omitempty"`
Locale string `json:"locale"`
Timezone StringMap `json:"timezone"`
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,omitempty"`
LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
AcceptedTermsOfServiceId string `json:"accepted_terms_of_service_id,omitempty"` // TODO remove this field when new TOS user action table is created
Id string `json:"id"`
CreateAt int64 `json:"create_at,omitempty"`
UpdateAt int64 `json:"update_at,omitempty"`
DeleteAt int64 `json:"delete_at"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
AuthData *string `json:"auth_data,omitempty"`
AuthService string `json:"auth_service"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified,omitempty"`
Nickname string `json:"nickname"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Position string `json:"position"`
Roles string `json:"roles"`
AllowMarketing bool `json:"allow_marketing,omitempty"`
Props StringMap `json:"props,omitempty"`
NotifyProps StringMap `json:"notify_props,omitempty"`
LastPasswordUpdate int64 `json:"last_password_update,omitempty"`
LastPictureUpdate int64 `json:"last_picture_update,omitempty"`
FailedAttempts int `json:"failed_attempts,omitempty"`
Locale string `json:"locale"`
Timezone StringMap `json:"timezone"`
MfaActive bool `json:"mfa_active,omitempty"`
MfaSecret string `json:"mfa_secret,omitempty"`
LastActivityAt int64 `db:"-" json:"last_activity_at,omitempty"`
}
type UserPatch struct {

46
model/user_terms_of_Service_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func TestUserTermsOfServiceIsValid(t *testing.T) {
s := UserTermsOfService{}
if err := s.IsValid(); err == nil {
t.Fatal("should be invalid")
}
s.UserId = NewId()
if err := s.IsValid(); err == nil {
t.Fatal("should be invalid")
}
s.TermsOfServiceId = NewId()
if err := s.IsValid(); err == nil {
t.Fatal("should be invalid")
}
s.CreateAt = GetMillis()
if err := s.IsValid(); err != nil {
t.Fatal("should be valid")
}
}
func TestUserTermsOfServiceJson(t *testing.T) {
o := UserTermsOfService{
UserId: NewId(),
TermsOfServiceId: NewId(),
CreateAt: GetMillis(),
}
j := o.ToJson()
ro := UserTermsOfServiceFromJson(strings.NewReader(j))
assert.NotNil(t, ro)
assert.Equal(t, o, *ro)
}

61
model/user_terms_of_service.go Обычный файл
Просмотреть файл

@@ -0,0 +1,61 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type UserTermsOfService struct {
UserId string `json:"user_id"`
TermsOfServiceId string `json:"terms_of_service_id"`
CreateAt int64 `json:"create_at"`
}
func (ut *UserTermsOfService) IsValid() *AppError {
if len(ut.UserId) != 26 {
return InvalidUserTermsOfServiceError("user_id", ut.UserId)
}
if len(ut.TermsOfServiceId) != 26 {
return InvalidUserTermsOfServiceError("terms_of_service_id", ut.UserId)
}
if ut.CreateAt == 0 {
return InvalidUserTermsOfServiceError("create_at", ut.UserId)
}
return nil
}
func (ut *UserTermsOfService) ToJson() string {
b, _ := json.Marshal(ut)
return string(b)
}
func (ut *UserTermsOfService) PreSave() {
if ut.UserId == "" {
ut.UserId = NewId()
}
ut.CreateAt = GetMillis()
}
func UserTermsOfServiceFromJson(data io.Reader) *UserTermsOfService {
var userTermsOfService *UserTermsOfService
json.NewDecoder(data).Decode(&userTermsOfService)
return userTermsOfService
}
func InvalidUserTermsOfServiceError(fieldName string, userTermsOfServiceId string) *AppError {
id := fmt.Sprintf("model.user_terms_of_service.is_valid.%s.app_error", fieldName)
details := ""
if userTermsOfServiceId != "" {
details = "user_terms_of_service_user_id=" + userTermsOfServiceId
}
return NewAppError("UserTermsOfService.IsValid", id, nil, details, http.StatusBadRequest)
}

Просмотреть файл

@@ -13,6 +13,7 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
"5.5.0",
"5.4.0",
"5.3.0",
"5.2.0",

Просмотреть файл

@@ -106,6 +106,11 @@ type API interface {
// GetTeamByName gets a team by its name.
GetTeamByName(name string) (*model.Team, *model.AppError)
// GetTeamsUnreadForUser gets the unread message and mention counts for each team to which the given user belongs.
//
// Minimum server version: 5.6
GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError)
// UpdateTeam updates a team.
UpdateTeam(team *model.Team) (*model.Team, *model.AppError)
@@ -259,6 +264,13 @@ type API interface {
// Minimum server version: 5.6
GetProfileImage(userId string) ([]byte, *model.AppError)
// GetEmojiList returns a page of custom emoji on the system.
//
// The sortBy parameter can be: "name".
//
// Minimum server version: 5.6
GetEmojiList(sortBy string, page, perPage int) ([]*model.Emoji, *model.AppError)
// GetEmojiByName gets an emoji by it's name.
//
// Minimum server version: 5.6
@@ -297,6 +309,40 @@ type API interface {
// Minimum server version: 5.6
GetEmojiImage(emojiId string) ([]byte, string, *model.AppError)
// UploadFile will upload a file to a channel using a multipart request, to be later attached to a post.
//
// Minimum server version: 5.6
UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError)
// Plugin Section
// GetPlugins will return a list of plugin manifests for currently active plugins.
//
// Minimum server version: 5.6
GetPlugins() ([]*model.Manifest, *model.AppError)
// EnablePlugin will enable an plugin installed.
//
// Minimum server version: 5.6
EnablePlugin(id string) *model.AppError
// DisablePlugin will disable an enabled plugin.
//
// Minimum server version: 5.6
DisablePlugin(id string) *model.AppError
// RemovePlugin will disable and delete a plugin.
//
// Minimum server version: 5.6
RemovePlugin(id string) *model.AppError
// GetPluginStatus will return the status of a plugin.
//
// Minimum server version: 5.6
GetPluginStatus(id string) (*model.PluginStatus, *model.AppError)
// KV Store Section
// KVSet will store a key-value pair, unique per plugin.
KVSet(key string, value []byte) *model.AppError

Просмотреть файл

@@ -1209,6 +1209,35 @@ func (s *apiRPCServer) GetTeamByName(args *Z_GetTeamByNameArgs, returns *Z_GetTe
return nil
}
type Z_GetTeamsUnreadForUserArgs struct {
A string
}
type Z_GetTeamsUnreadForUserReturns struct {
A []*model.TeamUnread
B *model.AppError
}
func (g *apiRPCClient) GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError) {
_args := &Z_GetTeamsUnreadForUserArgs{userId}
_returns := &Z_GetTeamsUnreadForUserReturns{}
if err := g.client.Call("Plugin.GetTeamsUnreadForUser", _args, _returns); err != nil {
log.Printf("RPC call to GetTeamsUnreadForUser API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetTeamsUnreadForUser(args *Z_GetTeamsUnreadForUserArgs, returns *Z_GetTeamsUnreadForUserReturns) error {
if hook, ok := s.impl.(interface {
GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetTeamsUnreadForUser(args.A)
} else {
return encodableError(fmt.Errorf("API GetTeamsUnreadForUser called but not implemented."))
}
return nil
}
type Z_UpdateTeamArgs struct {
A *model.Team
}
@@ -2432,6 +2461,37 @@ func (s *apiRPCServer) GetProfileImage(args *Z_GetProfileImageArgs, returns *Z_G
return nil
}
type Z_GetEmojiListArgs struct {
A string
B int
C int
}
type Z_GetEmojiListReturns struct {
A []*model.Emoji
B *model.AppError
}
func (g *apiRPCClient) GetEmojiList(sortBy string, page, perPage int) ([]*model.Emoji, *model.AppError) {
_args := &Z_GetEmojiListArgs{sortBy, page, perPage}
_returns := &Z_GetEmojiListReturns{}
if err := g.client.Call("Plugin.GetEmojiList", _args, _returns); err != nil {
log.Printf("RPC call to GetEmojiList API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetEmojiList(args *Z_GetEmojiListArgs, returns *Z_GetEmojiListReturns) error {
if hook, ok := s.impl.(interface {
GetEmojiList(sortBy string, page, perPage int) ([]*model.Emoji, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetEmojiList(args.A, args.B, args.C)
} else {
return encodableError(fmt.Errorf("API GetEmojiList called but not implemented."))
}
return nil
}
type Z_GetEmojiByNameArgs struct {
A string
}
@@ -2637,6 +2697,178 @@ func (s *apiRPCServer) GetEmojiImage(args *Z_GetEmojiImageArgs, returns *Z_GetEm
return nil
}
type Z_UploadFileArgs struct {
A []byte
B string
C string
}
type Z_UploadFileReturns struct {
A *model.FileInfo
B *model.AppError
}
func (g *apiRPCClient) UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) {
_args := &Z_UploadFileArgs{data, channelId, filename}
_returns := &Z_UploadFileReturns{}
if err := g.client.Call("Plugin.UploadFile", _args, _returns); err != nil {
log.Printf("RPC call to UploadFile API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) UploadFile(args *Z_UploadFileArgs, returns *Z_UploadFileReturns) error {
if hook, ok := s.impl.(interface {
UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError)
}); ok {
returns.A, returns.B = hook.UploadFile(args.A, args.B, args.C)
} else {
return encodableError(fmt.Errorf("API UploadFile called but not implemented."))
}
return nil
}
type Z_GetPluginsArgs struct {
}
type Z_GetPluginsReturns struct {
A []*model.Manifest
B *model.AppError
}
func (g *apiRPCClient) GetPlugins() ([]*model.Manifest, *model.AppError) {
_args := &Z_GetPluginsArgs{}
_returns := &Z_GetPluginsReturns{}
if err := g.client.Call("Plugin.GetPlugins", _args, _returns); err != nil {
log.Printf("RPC call to GetPlugins API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetPlugins(args *Z_GetPluginsArgs, returns *Z_GetPluginsReturns) error {
if hook, ok := s.impl.(interface {
GetPlugins() ([]*model.Manifest, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetPlugins()
} else {
return encodableError(fmt.Errorf("API GetPlugins called but not implemented."))
}
return nil
}
type Z_GetPluginStatusArgs struct {
A string
}
type Z_GetPluginStatusReturns struct {
A *model.PluginStatus
B *model.AppError
}
func (g *apiRPCClient) GetPluginStatus(id string) (*model.PluginStatus, *model.AppError) {
_args := &Z_GetPluginStatusArgs{id}
_returns := &Z_GetPluginStatusReturns{}
if err := g.client.Call("Plugin.GetPluginStatus", _args, _returns); err != nil {
log.Printf("RPC call to GetPluginStatus API failed: %s", err.Error())
}
return _returns.A, _returns.B
}
func (s *apiRPCServer) GetPluginStatus(args *Z_GetPluginStatusArgs, returns *Z_GetPluginStatusReturns) error {
if hook, ok := s.impl.(interface {
GetPluginStatus(id string) (*model.PluginStatus, *model.AppError)
}); ok {
returns.A, returns.B = hook.GetPluginStatus(args.A)
} else {
return encodableError(fmt.Errorf("API GetPluginStatus called but not implemented."))
}
return nil
}
type Z_EnablePluginArgs struct {
A string
}
type Z_EnablePluginReturns struct {
A *model.AppError
}
func (g *apiRPCClient) EnablePlugin(id string) *model.AppError {
_args := &Z_EnablePluginArgs{id}
_returns := &Z_EnablePluginReturns{}
if err := g.client.Call("Plugin.EnablePlugin", _args, _returns); err != nil {
log.Printf("RPC call to EnablePlugin API failed: %s", err.Error())
}
return _returns.A
}
func (s *apiRPCServer) EnablePlugin(args *Z_EnablePluginArgs, returns *Z_EnablePluginReturns) error {
if hook, ok := s.impl.(interface {
EnablePlugin(id string) *model.AppError
}); ok {
returns.A = hook.EnablePlugin(args.A)
} else {
return encodableError(fmt.Errorf("API EnablePlugin called but not implemented."))
}
return nil
}
type Z_DisablePluginArgs struct {
A string
}
type Z_DisablePluginReturns struct {
A *model.AppError
}
func (g *apiRPCClient) DisablePlugin(id string) *model.AppError {
_args := &Z_DisablePluginArgs{id}
_returns := &Z_DisablePluginReturns{}
if err := g.client.Call("Plugin.DisablePlugin", _args, _returns); err != nil {
log.Printf("RPC call to DisablePlugin API failed: %s", err.Error())
}
return _returns.A
}
func (s *apiRPCServer) DisablePlugin(args *Z_DisablePluginArgs, returns *Z_DisablePluginReturns) error {
if hook, ok := s.impl.(interface {
DisablePlugin(id string) *model.AppError
}); ok {
returns.A = hook.DisablePlugin(args.A)
} else {
return encodableError(fmt.Errorf("API DisablePlugin called but not implemented."))
}
return nil
}
type Z_RemovePluginArgs struct {
A string
}
type Z_RemovePluginReturns struct {
A *model.AppError
}
func (g *apiRPCClient) RemovePlugin(id string) *model.AppError {
_args := &Z_RemovePluginArgs{id}
_returns := &Z_RemovePluginReturns{}
if err := g.client.Call("Plugin.RemovePlugin", _args, _returns); err != nil {
log.Printf("RPC call to RemovePlugin API failed: %s", err.Error())
}
return _returns.A
}
func (s *apiRPCServer) RemovePlugin(args *Z_RemovePluginArgs, returns *Z_RemovePluginReturns) error {
if hook, ok := s.impl.(interface {
RemovePlugin(id string) *model.AppError
}); ok {
returns.A = hook.RemovePlugin(args.A)
} else {
return encodableError(fmt.Errorf("API RemovePlugin called but not implemented."))
}
return nil
}
type Z_KVSetArgs struct {
A string
B []byte

Просмотреть файл

@@ -166,6 +166,16 @@ func (env *Environment) Activate(id string) (manifest *model.Manifest, activated
env.activePlugins.Store(pluginInfo.Manifest.Id, activePlugin)
}()
if pluginInfo.Manifest.MinServerVersion != "" {
fulfilled, err := pluginInfo.Manifest.MeetMinServerVersion(model.CurrentVersion)
if err != nil {
return nil, false, fmt.Errorf("%v: %v", err.Error(), id)
}
if !fulfilled {
return nil, false, fmt.Errorf("plugin requires Mattermost %v: %v", pluginInfo.Manifest.MinServerVersion, id)
}
}
componentActivated := false
if pluginInfo.Manifest.HasWebapp() {

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше