From 809a16458f7483a2b762cd546493780fea6220ea Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Wed, 7 Feb 2018 13:41:15 +0530 Subject: [PATCH 01/14] Abort on critical error during server startup (#8204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only a handful of critical errors are present in the codebase. They all occur during server startup (in `app.StartServer()`). Currently, when one of these critical error occurs, it is simpled mentionned in the logs – then the error is discarded, and the app attempts to continue the execution (and probably fails pretty quickly in a weird way). Rather than continuing operations in an unknow state, these errors should trigger a clean exit. This commit rewrites critical startup errors to be correctly propagated, logged, and then terminate the command execution. Additionnaly, it makes the server return a proper error code to the shell. --- api/apitestlib.go | 6 ++++- api4/apitestlib.go | 6 ++++- app/app_test.go | 3 ++- app/apptestlib.go | 6 ++++- app/server.go | 12 +++++----- app/server_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ cmd/platform/server.go | 9 ++++++-- cmd/platform/test.go | 12 ++++++++-- web/web_test.go | 5 ++++- 9 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 app/server_test.go diff --git a/api/apitestlib.go b/api/apitestlib.go index 8d7f549027..bae00927a0 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -105,7 +105,11 @@ func setupTestHelper(enterprise bool) *TestHelper { if testStore != nil { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) } - th.App.StartServer() + serverErr := th.App.StartServer() + if serverErr != nil { + panic(serverErr) + } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) api4.Init(th.App, th.App.Srv.Router, false) Init(th.App, th.App.Srv.Router) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index a7e64ae848..ccdb4c2063 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -113,7 +113,11 @@ func setupTestHelper(enterprise bool) *TestHelper { if testStore != nil { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) } - th.App.StartServer() + serverErr := th.App.StartServer() + if serverErr != nil { + panic(serverErr) + } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) Init(th.App, th.App.Srv.Router, true) wsapi.Init(th.App, th.App.Srv.WebSocketRouter) diff --git a/app/app_test.go b/app/app_test.go index 25b19ead8b..09f8725d7e 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -51,7 +51,8 @@ func TestAppRace(t *testing.T) { a, err := New() require.NoError(t, err) a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - a.StartServer() + serverErr := a.StartServer() + require.NoError(t, serverErr) a.Shutdown() } } diff --git a/app/apptestlib.go b/app/apptestlib.go index 09afc8f76f..016a68becf 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -96,7 +96,11 @@ func setupTestHelper(enterprise bool) *TestHelper { if testStore != nil { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) } - th.App.StartServer() + serverErr := th.App.StartServer() + if serverErr != nil { + panic(serverErr) + } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) th.App.Srv.Store.MarkSystemRanUnitTests() diff --git a/app/server.go b/app/server.go index 1659908b6c..afa282ad6b 100644 --- a/app/server.go +++ b/app/server.go @@ -17,6 +17,7 @@ import ( l4g "github.com/alecthomas/log4go" "github.com/gorilla/handlers" "github.com/gorilla/mux" + "github.com/pkg/errors" "golang.org/x/crypto/acme/autocert" "github.com/mattermost/mattermost-server/model" @@ -116,7 +117,7 @@ func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, url.String(), http.StatusFound) } -func (a *App) StartServer() { +func (a *App) StartServer() error { l4g.Info(utils.T("api.server.start_server.starting.info")) var handler http.Handler = &CorsWrapper{a.Config, a.Srv.Router} @@ -126,8 +127,7 @@ func (a *App) StartServer() { rateLimiter, err := NewRateLimiter(&a.Config().RateLimitSettings) if err != nil { - l4g.Critical(err.Error()) - return + return err } a.Srv.RateLimiter = rateLimiter @@ -151,8 +151,8 @@ func (a *App) StartServer() { listener, err := net.Listen("tcp", addr) if err != nil { - l4g.Critical(utils.T("api.server.start_server.starting.critical"), err) - return + errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err) + return err } a.Srv.ListenAddr = listener.Addr().(*net.TCPAddr) @@ -214,6 +214,8 @@ func (a *App) StartServer() { } close(a.Srv.didFinishListen) }() + + return nil } type tcpKeepAliveListener struct { diff --git a/app/server_test.go b/app/server_test.go new file mode 100644 index 0000000000..de358b9767 --- /dev/null +++ b/app/server_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/stretchr/testify/require" +) + +func TestStartServerSuccess(t *testing.T) { + a, err := New() + require.NoError(t, err) + + a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) + serverErr := a.StartServer() + a.Shutdown() + require.NoError(t, serverErr) +} + +func TestStartServerRateLimiterCriticalError(t *testing.T) { + a, err := New() + require.NoError(t, err) + + // Attempt to use Rate Limiter with an invalid config + a.UpdateConfig(func(cfg *model.Config) { + *cfg.RateLimitSettings.Enable = true + *cfg.RateLimitSettings.MaxBurst = -100 + }) + + serverErr := a.StartServer() + a.Shutdown() + require.Error(t, serverErr) +} + +func TestStartServerPortUnavailable(t *testing.T) { + a, err := New() + require.NoError(t, err) + + // Attempt to listen on a system-reserved port + a.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.ListenAddress = ":21" + }) + + serverErr := a.StartServer() + a.Shutdown() + require.Error(t, serverErr) +} diff --git a/cmd/platform/server.go b/cmd/platform/server.go index e3742cef62..a8a6e8923f 100644 --- a/cmd/platform/server.go +++ b/cmd/platform/server.go @@ -53,7 +53,7 @@ func runServer(configFileLocation string, disableConfigWatch bool) error { a, err := app.New(options...) if err != nil { - l4g.Error(err.Error()) + l4g.Critical(err.Error()) return err } defer a.Shutdown() @@ -87,7 +87,12 @@ func runServer(configFileLocation string, disableConfigWatch bool) error { } }) - a.StartServer() + serverErr := a.StartServer() + if serverErr != nil { + l4g.Critical(serverErr.Error()) + return serverErr + } + api4.Init(a, a.Srv.Router, false) api3 := api.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) diff --git a/cmd/platform/test.go b/cmd/platform/test.go index 036df07de2..9ab3fbb367 100644 --- a/cmd/platform/test.go +++ b/cmd/platform/test.go @@ -53,7 +53,11 @@ func webClientTestsCmdF(cmd *cobra.Command, args []string) error { defer a.Shutdown() utils.InitTranslations(a.Config().LocalizationSettings) - a.StartServer() + serverErr := a.StartServer() + if serverErr != nil { + return serverErr + } + api4.Init(a, a.Srv.Router, false) api.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) @@ -71,7 +75,11 @@ func serverForWebClientTestsCmdF(cmd *cobra.Command, args []string) error { defer a.Shutdown() utils.InitTranslations(a.Config().LocalizationSettings) - a.StartServer() + serverErr := a.StartServer() + if serverErr != nil { + return serverErr + } + api4.Init(a, a.Srv.Router, false) api.Init(a, a.Srv.Router) wsapi.Init(a, a.Srv.WebSocketRouter) diff --git a/web/web_test.go b/web/web_test.go index 21a7968b3c..c8d64c61d1 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -44,7 +44,10 @@ func Setup() *app.App { } prevListenAddress := *a.Config().ServiceSettings.ListenAddress a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - a.StartServer() + serverErr := a.StartServer() + if serverErr != nil { + panic(serverErr) + } a.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress }) api4.Init(a, a.Srv.Router, false) api3 := api.Init(a, a.Srv.Router) From 121712ce5d61cd0bb35efa5bacd59f7f9daff326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Wed, 7 Feb 2018 10:42:55 +0100 Subject: [PATCH 02/14] Remove fmt.Println trace on api4 tests (#8212) --- api4/apitestlib.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index ccdb4c2063..b55ce7cbfc 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -302,8 +302,7 @@ func (me *TestHelper) CreateUserWithClient(client *model.Client4) *model.User { } utils.DisableDebugLogForTest() - ruser, r := client.CreateUser(user) - fmt.Println(r) + ruser, _ := client.CreateUser(user) ruser.Password = "Password1" store.Must(me.App.Srv.Store.User().VerifyEmail(ruser.Id)) utils.EnableDebugLogForTest() From b2ee5077931013d308aaf60d790d341e2cb0c3e3 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 7 Feb 2018 07:21:40 -0600 Subject: [PATCH 03/14] allow plugins to set command hints (#8214) --- app/plugin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/app/plugin.go b/app/plugin.go index 3f06a000f3..fe671d26ac 100644 --- a/app/plugin.go +++ b/app/plugin.go @@ -565,6 +565,7 @@ func (a *App) RegisterPluginCommand(pluginId string, command *model.Command) err TeamId: command.TeamId, AutoComplete: command.AutoComplete, AutoCompleteDesc: command.AutoCompleteDesc, + AutoCompleteHint: command.AutoCompleteHint, DisplayName: command.DisplayName, } From d3e934d07ac0a58a24a435ea7c5b3bd222ef509a Mon Sep 17 00:00:00 2001 From: Jonathan Date: Wed, 7 Feb 2018 09:02:46 -0500 Subject: [PATCH 04/14] XYZ-35: Added Support for GlobalRelay Compliance Export Format * Added username to ChannelMemberHistory struct in anticipation of supporting GlobalRelay in Compliance Export * Removed translation from debug output - this makes it complicated to use utils functions from tests in the enterprise repo * Added an advanced email function that allows for greater control over message details. Updated MessageExport config to support GlobalRelay. Added attachment support to InBucket unit tests * Moving templates in from enterprise to solve test issues * Added export format to diagnostics * Changed email attachment code to use FileBackend so that S3 storage is properly supported --- app/diagnostics.go | 1 + i18n/en.json | 28 +++--- model/channel_member_history.go | 5 +- model/config.go | 21 +++++ model/config_test.go | 52 ++++++++++- model/message_export.go | 1 + .../sqlstore/channel_member_history_store.go | 12 ++- store/sqlstore/compliance_store.go | 3 +- .../storetest/channel_member_history_store.go | 16 ++++ store/storetest/compliance_store.go | 9 +- templates/globalrelay_compliance_export.html | 91 +++++++++++++++++++ ...globalrelay_compliance_export_message.html | 8 ++ ...lay_compliance_export_participant_row.html | 10 ++ utils/html.go | 8 +- utils/inbucket.go | 52 +++++++++-- utils/mail.go | 60 +++++++++--- utils/mail_test.go | 87 +++++++++++++++++- 17 files changed, 413 insertions(+), 51 deletions(-) create mode 100644 templates/globalrelay_compliance_export.html create mode 100644 templates/globalrelay_compliance_export_message.html create mode 100644 templates/globalrelay_compliance_export_participant_row.html diff --git a/app/diagnostics.go b/app/diagnostics.go index 809d9ff1e4..6d83d3a890 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -501,6 +501,7 @@ func (a *App) trackConfig() { a.SendDiagnostic(TRACK_CONFIG_MESSAGE_EXPORT, map[string]interface{}{ "enable_message_export": *cfg.MessageExportSettings.EnableExport, + "export_format": *cfg.MessageExportSettings.ExportFormat, "daily_run_time": *cfg.MessageExportSettings.DailyRunTime, "default_export_from_timestamp": *cfg.MessageExportSettings.ExportFromTimestamp, "batch_size": *cfg.MessageExportSettings.BatchSize, diff --git a/i18n/en.json b/i18n/en.json index d983e88556..1a04aeeed2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -131,10 +131,6 @@ "id": "api.admin.upload_brand_image.too_large.app_error", "translation": "Unable to upload file. File is too large." }, - { - "id": "api.api.init.parsing_templates.debug", - "translation": "Parsing server templates at %v" - }, { "id": "api.api.init.parsing_templates.error", "translation": "Failed to parse server templates %v" @@ -4858,6 +4854,14 @@ "id": "model.config.is_valid.message_export.batch_size.app_error", "translation": "Message export job BatchSize must be a positive integer" }, + { + "id": "model.config.is_valid.message_export.export_type.app_error", + "translation": "Message export job ExportFormat must be one of either 'actiance' or 'globalrelay'" + }, + { + "id": "model.config.is_valid.message_export.global_relay_email_address.app_error", + "translation": "Message export job GlobalRelayEmailAddress must be set to a valid email address" + }, { "id": "model.config.is_valid.message_export.daily_runtime.app_error", "translation": "Message export job DailyRuntime must be a 24-hour time stamp in the form HH:MM." @@ -7082,6 +7086,10 @@ "id": "utils.mail.new_client.auth.app_error", "translation": "Failed to authenticate on SMTP server" }, + { + "id": "utils.mail.sendMail.attachments.write_error", + "translation": "Failed to write attachment to email" + }, { "id": "utils.mail.new_client.helo.error", "translation": "Failed to to set the HELO to SMTP server %v" @@ -7158,10 +7166,6 @@ "id": "web.create_dir.error", "translation": "Failed to create directory watcher %v" }, - { - "id": "web.dir_fail.error", - "translation": "Failed in directory watcher %v" - }, { "id": "web.do_load_channel.error", "translation": "Error in getting users profile for id=%v forcing logout" @@ -7266,18 +7270,10 @@ "id": "web.parsing_templates.debug", "translation": "Parsing templates at %v" }, - { - "id": "web.parsing_templates.error", - "translation": "Failed to parse templates %v" - }, { "id": "web.post_permalink.app_error", "translation": "Invalid Post ID" }, - { - "id": "web.reparse_templates.info", - "translation": "Re-parsing templates because of modified file %v" - }, { "id": "web.reset_password.expired_link.app_error", "translation": "The password reset link has expired" diff --git a/model/channel_member_history.go b/model/channel_member_history.go index bc71b580a7..47c59d54eb 100644 --- a/model/channel_member_history.go +++ b/model/channel_member_history.go @@ -6,7 +6,10 @@ package model type ChannelMemberHistory struct { ChannelId string UserId string - UserEmail string `db:"Email"` JoinTime int64 LeaveTime *int64 + + // these two fields are never set in the database - when we SELECT, we join on Users to get them + UserEmail string `db:"Email"` + Username string } diff --git a/model/config.go b/model/config.go index 20011f7cb1..9010eaeae5 100644 --- a/model/config.go +++ b/model/config.go @@ -158,6 +158,9 @@ const ( PLUGIN_SETTINGS_DEFAULT_DIRECTORY = "./plugins" PLUGIN_SETTINGS_DEFAULT_CLIENT_DIRECTORY = "./client/plugins" + + COMPLIANCE_EXPORT_TYPE_ACTIANCE = "actiance" + COMPLIANCE_EXPORT_TYPE_GLOBALRELAY = "globalrelay" ) type ServiceSettings struct { @@ -1623,9 +1626,13 @@ func (s *PluginSettings) SetDefaults() { type MessageExportSettings struct { EnableExport *bool + ExportFormat *string DailyRunTime *string ExportFromTimestamp *int64 BatchSize *int + + // formatter-specific settings - these are only expected to be non-nil if ExportFormat is set to the associated format + GlobalRelayEmailAddress *string } func (s *MessageExportSettings) SetDefaults() { @@ -1633,6 +1640,10 @@ func (s *MessageExportSettings) SetDefaults() { s.EnableExport = NewBool(false) } + if s.ExportFormat == nil { + s.ExportFormat = NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE) + } + if s.DailyRunTime == nil { s.DailyRunTime = NewString("01:00") } @@ -2170,6 +2181,16 @@ func (mes *MessageExportSettings) isValid(fs FileSettings) *AppError { return NewAppError("Config.IsValid", "model.config.is_valid.message_export.daily_runtime.app_error", nil, err.Error(), http.StatusBadRequest) } else if mes.BatchSize == nil || *mes.BatchSize < 0 { return NewAppError("Config.IsValid", "model.config.is_valid.message_export.batch_size.app_error", nil, "", http.StatusBadRequest) + } else if mes.ExportFormat == nil || (*mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_ACTIANCE && *mes.ExportFormat != COMPLIANCE_EXPORT_TYPE_GLOBALRELAY) { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.export_type.app_error", nil, "", http.StatusBadRequest) + } + + if *mes.ExportFormat == COMPLIANCE_EXPORT_TYPE_GLOBALRELAY { + // validating email addresses is hard - just make sure it contains an '@' sign + // see https://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address + if mes.GlobalRelayEmailAddress == nil || !strings.Contains(*mes.GlobalRelayEmailAddress, "@") { + return NewAppError("Config.IsValid", "model.config.is_valid.message_export.global_relay_email_address.app_error", nil, "", http.StatusBadRequest) + } } } return nil diff --git a/model/config_test.go b/model/config_test.go index 5510c40d04..919f73fd78 100644 --- a/model/config_test.go +++ b/model/config_test.go @@ -136,7 +136,7 @@ func TestMessageExportSettingsIsValidBatchSizeInvalid(t *testing.T) { require.Error(t, mes.isValid(*fs)) } -func TestMessageExportSettingsIsValid(t *testing.T) { +func TestMessageExportSettingsIsValidExportFormatInvalid(t *testing.T) { fs := &FileSettings{ DriverName: NewString("foo"), // bypass file location check } @@ -147,6 +147,55 @@ func TestMessageExportSettingsIsValid(t *testing.T) { BatchSize: NewInt(100), } + // should fail fast because export format isn't set + require.Error(t, mes.isValid(*fs)) +} + +func TestMessageExportSettingsIsValidGlobalRelayEmailAddressInvalid(t *testing.T) { + fs := &FileSettings{ + DriverName: NewString("foo"), // bypass file location check + } + mes := &MessageExportSettings{ + EnableExport: NewBool(true), + ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_GLOBALRELAY), + ExportFromTimestamp: NewInt64(0), + DailyRunTime: NewString("15:04"), + BatchSize: NewInt(100), + } + + // should fail fast because global relay email address isn't set + require.Error(t, mes.isValid(*fs)) +} + +func TestMessageExportSettingsIsValidActiance(t *testing.T) { + fs := &FileSettings{ + DriverName: NewString("foo"), // bypass file location check + } + mes := &MessageExportSettings{ + EnableExport: NewBool(true), + ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_ACTIANCE), + ExportFromTimestamp: NewInt64(0), + DailyRunTime: NewString("15:04"), + BatchSize: NewInt(100), + } + + // should pass because everything is valid + require.Nil(t, mes.isValid(*fs)) +} + +func TestMessageExportSettingsIsValidGlobalRelay(t *testing.T) { + fs := &FileSettings{ + DriverName: NewString("foo"), // bypass file location check + } + mes := &MessageExportSettings{ + EnableExport: NewBool(true), + ExportFormat: NewString(COMPLIANCE_EXPORT_TYPE_GLOBALRELAY), + ExportFromTimestamp: NewInt64(0), + DailyRunTime: NewString("15:04"), + BatchSize: NewInt(100), + GlobalRelayEmailAddress: NewString("test@mattermost.com"), + } + // should pass because everything is valid require.Nil(t, mes.isValid(*fs)) } @@ -159,6 +208,7 @@ func TestMessageExportSetDefaults(t *testing.T) { require.Equal(t, "01:00", *mes.DailyRunTime) require.Equal(t, int64(0), *mes.ExportFromTimestamp) require.Equal(t, 10000, *mes.BatchSize) + require.Equal(t, COMPLIANCE_EXPORT_TYPE_ACTIANCE, *mes.ExportFormat) } func TestMessageExportSetDefaultsExportEnabledExportFromTimestampNil(t *testing.T) { diff --git a/model/message_export.go b/model/message_export.go index b59b114d46..22641deee3 100644 --- a/model/message_export.go +++ b/model/message_export.go @@ -9,6 +9,7 @@ type MessageExport struct { UserId *string UserEmail *string + Username *string PostId *string PostCreateAt *int64 diff --git a/store/sqlstore/channel_member_history_store.go b/store/sqlstore/channel_member_history_store.go index 182f37ce99..0b86aac282 100644 --- a/store/sqlstore/channel_member_history_store.go +++ b/store/sqlstore/channel_member_history_store.go @@ -110,7 +110,8 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime query := ` SELECT cmh.*, - u.Email + u.Email, + u.Username FROM ChannelMemberHistory cmh INNER JOIN Users u ON cmh.UserId = u.Id WHERE cmh.ChannelId = :ChannelId @@ -130,9 +131,10 @@ func (s SqlChannelMemberHistoryStore) getFromChannelMemberHistoryTable(startTime func (s SqlChannelMemberHistoryStore) getFromChannelMembersTable(startTime int64, endTime int64, channelId string) ([]*model.ChannelMemberHistory, error) { query := ` SELECT DISTINCT - ch.ChannelId, - ch.UserId, - u.email + ch.ChannelId, + ch.UserId, + u.Email, + u.Username FROM ChannelMembers AS ch INNER JOIN Users AS u ON ch.UserId = u.id WHERE ch.ChannelId = :ChannelId` @@ -158,7 +160,7 @@ func (s SqlChannelMemberHistoryStore) PermanentDeleteBatch(endTime int64, limit query = `DELETE FROM ChannelMemberHistory WHERE ctid IN ( - SELECT ctid FROM ChannelMemberHistory + SELECT ctid FROM ChannelMemberHistory WHERE LeaveTime IS NOT NULL AND LeaveTime <= :EndTime LIMIT :Limit diff --git a/store/sqlstore/compliance_store.go b/store/sqlstore/compliance_store.go index a25b01548f..03d92d5e14 100644 --- a/store/sqlstore/compliance_store.go +++ b/store/sqlstore/compliance_store.go @@ -225,7 +225,8 @@ func (s SqlComplianceStore) MessageExport(after int64, limit int) store.StoreCha Channels.Id AS ChannelId, Channels.DisplayName AS ChannelDisplayName, Users.Id AS UserId, - Users.Email AS UserEmail + Users.Email AS UserEmail, + Users.Username FROM Posts LEFT OUTER JOIN Channels ON Posts.ChannelId = Channels.Id diff --git a/store/storetest/channel_member_history_store.go b/store/storetest/channel_member_history_store.go index 6fe73478cf..fa2e7a8faf 100644 --- a/store/storetest/channel_member_history_store.go +++ b/store/storetest/channel_member_history_store.go @@ -35,6 +35,7 @@ func testLogJoinEvent(t *testing.T, ss store.Store) { user := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user = *store.Must(ss.User().Save(&user)).(*model.User) @@ -57,6 +58,7 @@ func testLogLeaveEvent(t *testing.T, ss store.Store) { user := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user = *store.Must(ss.User().Save(&user)).(*model.User) @@ -82,6 +84,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) { user := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user = *store.Must(ss.User().Save(&user)).(*model.User) @@ -108,6 +111,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime, channelMembers[0].JoinTime) assert.Nil(t, channelMembers[0].LeaveTime) @@ -117,6 +121,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime, channelMembers[0].JoinTime) assert.Nil(t, channelMembers[0].LeaveTime) @@ -129,6 +134,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime, channelMembers[0].JoinTime) assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime) @@ -138,6 +144,7 @@ func testGetUsersInChannelAtChannelMemberHistory(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime, channelMembers[0].JoinTime) assert.Equal(t, leaveTime, *channelMembers[0].LeaveTime) @@ -160,6 +167,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { user := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user = *store.Must(ss.User().Save(&user)).(*model.User) @@ -192,6 +200,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime-500, channelMembers[0].JoinTime) assert.Equal(t, joinTime-100, *channelMembers[0].LeaveTime) @@ -201,6 +210,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime-100, channelMembers[0].JoinTime) assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime) @@ -210,6 +220,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime+100, channelMembers[0].JoinTime) assert.Equal(t, joinTime+500, *channelMembers[0].LeaveTime) @@ -219,6 +230,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime+100, channelMembers[0].JoinTime) assert.Equal(t, leaveTime-100, *channelMembers[0].LeaveTime) @@ -228,6 +240,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, joinTime-100, channelMembers[0].JoinTime) assert.Equal(t, leaveTime+100, *channelMembers[0].LeaveTime) @@ -237,6 +250,7 @@ func testGetUsersInChannelAtChannelMembers(t *testing.T, ss store.Store) { assert.Equal(t, channel.Id, channelMembers[0].ChannelId) assert.Equal(t, user.Id, channelMembers[0].UserId) assert.Equal(t, user.Email, channelMembers[0].UserEmail) + assert.Equal(t, user.Username, channelMembers[0].Username) assert.Equal(t, leaveTime+100, channelMembers[0].JoinTime) assert.Equal(t, leaveTime+200, *channelMembers[0].LeaveTime) } @@ -255,12 +269,14 @@ func testPermanentDeleteBatch(t *testing.T, ss store.Store) { user := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user = *store.Must(ss.User().Save(&user)).(*model.User) user2 := model.User{ Email: model.NewId() + "@mattermost.com", Nickname: model.NewId(), + Username: model.NewId(), } user2 = *store.Must(ss.User().Save(&user2)).(*model.User) diff --git a/store/storetest/compliance_store.go b/store/storetest/compliance_store.go index c5bd60f053..eb29bedc7d 100644 --- a/store/storetest/compliance_store.go +++ b/store/storetest/compliance_store.go @@ -341,7 +341,8 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) { // and two users that are a part of that team user1 := &model.User{ - Email: model.NewId(), + Email: model.NewId(), + Username: model.NewId(), } user1 = store.Must(ss.User().Save(user1)).(*model.User) store.Must(ss.Team().SaveMember(&model.TeamMember{ @@ -350,7 +351,8 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) { }, -1)) user2 := &model.User{ - Email: model.NewId(), + Email: model.NewId(), + Username: model.NewId(), } user2 = store.Must(ss.User().Save(user2)).(*model.User) store.Must(ss.Team().SaveMember(&model.TeamMember{ @@ -415,6 +417,7 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) { assert.Equal(t, channel.DisplayName, *messageExportMap[post1.Id].ChannelDisplayName) assert.Equal(t, user1.Id, *messageExportMap[post1.Id].UserId) assert.Equal(t, user1.Email, *messageExportMap[post1.Id].UserEmail) + assert.Equal(t, user1.Username, *messageExportMap[post1.Id].Username) // post2 was made by user1 in channel1 and team1 assert.Equal(t, post2.Id, *messageExportMap[post2.Id].PostId) @@ -424,6 +427,7 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) { assert.Equal(t, channel.DisplayName, *messageExportMap[post2.Id].ChannelDisplayName) assert.Equal(t, user1.Id, *messageExportMap[post2.Id].UserId) assert.Equal(t, user1.Email, *messageExportMap[post2.Id].UserEmail) + assert.Equal(t, user1.Username, *messageExportMap[post2.Id].Username) // post3 is a DM between user1 and user2 assert.Equal(t, post3.Id, *messageExportMap[post3.Id].PostId) @@ -432,4 +436,5 @@ func testComplianceMessageExport(t *testing.T, ss store.Store) { assert.Equal(t, directMessageChannel.Id, *messageExportMap[post3.Id].ChannelId) assert.Equal(t, user1.Id, *messageExportMap[post3.Id].UserId) assert.Equal(t, user1.Email, *messageExportMap[post3.Id].UserEmail) + assert.Equal(t, user1.Username, *messageExportMap[post3.Id].Username) } diff --git a/templates/globalrelay_compliance_export.html b/templates/globalrelay_compliance_export.html new file mode 100644 index 0000000000..91028d11cf --- /dev/null +++ b/templates/globalrelay_compliance_export.html @@ -0,0 +1,91 @@ +{{define "globalrelay_compliance_export"}} + + +

Mattermost Compliance Export

+ +

Conversation Summary

+
+
    +
  • Channel: {{.Props.ChannelName}}
  • +
  • Started: {{.Props.Started}}
  • +
  • Ended: {{.Props.Ended}}
  • +
  • Duration: {{.Props.Duration}} Minutes
  • +
+
+ + + + + + + + + + {{.Props.ParticipantRows}} +
Username
JoinedLeftDurationMessages
+ +

Messages

+
+
    + {{.Props.Messages}} +
+
+ +

Exported on {{.Props.ExportDate}}

+{{end}} \ No newline at end of file diff --git a/templates/globalrelay_compliance_export_message.html b/templates/globalrelay_compliance_export_message.html new file mode 100644 index 0000000000..3a47b29b70 --- /dev/null +++ b/templates/globalrelay_compliance_export_message.html @@ -0,0 +1,8 @@ +{{define "globalrelay_compliance_export_message"}} +
  • + {{.Props.SentTime}} + @{{.Props.Username}} + + {{.Props.Message}} +
  • +{{end}} diff --git a/templates/globalrelay_compliance_export_participant_row.html b/templates/globalrelay_compliance_export_participant_row.html new file mode 100644 index 0000000000..7a61e23eb7 --- /dev/null +++ b/templates/globalrelay_compliance_export_participant_row.html @@ -0,0 +1,10 @@ +{{define "globalrelay_compliance_export_participant_row"}} + + @{{.Props.Username}} + {{.Props.Email}} + {{.Props.Joined}} + {{.Props.Left}} + {{.Props.DurationMinutes}} Minutes + {{.Props.NumMessages}} + +{{end}} \ No newline at end of file diff --git a/utils/html.go b/utils/html.go index 02db8c97a0..6bbe55c6db 100644 --- a/utils/html.go +++ b/utils/html.go @@ -23,7 +23,7 @@ type HTMLTemplateWatcher struct { func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { templatesDir, _ := FindDir(directory) - l4g.Debug(T("api.api.init.parsing_templates.debug"), templatesDir) + l4g.Debug("Parsing server templates at %v", templatesDir) ret := &HTMLTemplateWatcher{ stop: make(chan struct{}), @@ -55,15 +55,15 @@ func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { return case event := <-watcher.Events: if event.Op&fsnotify.Write == fsnotify.Write { - l4g.Info(T("web.reparse_templates.info"), event.Name) + l4g.Info("Re-parsing templates because of modified file %v", event.Name) if htmlTemplates, err := template.ParseGlob(templatesDir + "*.html"); err != nil { - l4g.Error(T("web.parsing_templates.error"), err) + l4g.Error("Failed to parse templates %v", err) } else { ret.templates.Store(htmlTemplates) } } case err := <-watcher.Errors: - l4g.Error(T("web.dir_fail.error"), err) + l4g.Error("Failed in directory watcher %s", err) } } }() diff --git a/utils/inbucket.go b/utils/inbucket.go index 46011989bf..5c40d5757e 100644 --- a/utils/inbucket.go +++ b/utils/inbucket.go @@ -4,6 +4,7 @@ package utils import ( + "bytes" "encoding/json" "fmt" "io" @@ -37,6 +38,12 @@ type JSONMessageInbucket struct { Text string HTML string `json:"Html"` } + Attachments []struct { + Filename string + ContentType string `json:"content-type"` + DownloadLink string `json:"download-link"` + Bytes []byte `json:"-"` + } } func ParseEmail(email string) string { @@ -89,21 +96,54 @@ func GetMessageFromMailbox(email, id string) (results JSONMessageInbucket, err e var record JSONMessageInbucket url := fmt.Sprintf("%s%s%s/%s", getInbucketHost(), INBUCKET_API, parsedEmail, id) - req, err := http.NewRequest("GET", url, nil) + emailResponse, err := get(url) if err != nil { return record, err } + defer emailResponse.Body.Close() + + err = json.NewDecoder(emailResponse.Body).Decode(&record) + + // download attachments + if record.Attachments != nil && len(record.Attachments) > 0 { + for i := range record.Attachments { + if bytes, err := downloadAttachment(record.Attachments[i].DownloadLink); err != nil { + return record, err + } else { + record.Attachments[i].Bytes = make([]byte, len(bytes)) + copy(record.Attachments[i].Bytes, bytes) + } + } + } + + return record, err +} + +func downloadAttachment(url string) ([]byte, error) { + attachmentResponse, err := get(url) + if err != nil { + return nil, err + } + defer attachmentResponse.Body.Close() + + buf := new(bytes.Buffer) + io.Copy(buf, attachmentResponse.Body) + return buf.Bytes(), nil +} + +func get(url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } client := &http.Client{} - resp, err := client.Do(req) if err != nil { - return record, err + return nil, err } - defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&record) - return record, err + return resp, nil } func DeleteMailBox(email string) (err error) { diff --git a/utils/mail.go b/utils/mail.go index b0289da5ee..4c8a505af8 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -15,6 +15,8 @@ import ( "net/http" + "io" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/html2text" "github.com/mattermost/mattermost-server/model" @@ -104,36 +106,72 @@ func TestConnection(config *model.Config) { } func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *model.AppError { + fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail} + return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config) +} + +// allows for sending an email with attachments and differing MIME/SMTP recipients +func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { + return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config) +} + +func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 { return nil } - l4g.Debug(T("utils.mail.send_mail.sending.debug"), to, subject) + l4g.Debug(T("utils.mail.send_mail.sending.debug"), mimeTo, subject) htmlMessage := "\r\n" + htmlBody + "" - fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail} - txtBody, err := html2text.FromString(htmlBody) if err != nil { l4g.Warn(err) txtBody = "" } - m := gomail.NewMessage(gomail.SetCharset("UTF-8")) - m.SetHeaders(map[string][]string{ - "From": {fromMail.String()}, - "To": {to}, + headers := map[string][]string{ + "From": {from.String()}, + "To": {mimeTo}, "Subject": {encodeRFC2047Word(subject)}, "Content-Transfer-Encoding": {"8bit"}, "Auto-Submitted": {"auto-generated"}, "Precedence": {"bulk"}, - }) - m.SetDateHeader("Date", time.Now()) + } + if mimeHeaders != nil { + for k, v := range mimeHeaders { + headers[k] = []string{encodeRFC2047Word(v)} + } + } + m := gomail.NewMessage(gomail.SetCharset("UTF-8")) + m.SetHeaders(headers) + m.SetDateHeader("Date", time.Now()) m.SetBody("text/plain", txtBody) m.AddAlternative("text/html", htmlMessage) + if attachments != nil { + fileBackend, err := NewFileBackend(&config.FileSettings) + if err != nil { + return err + } + + for _, fileInfo := range attachments { + m.Attach(fileInfo.Name, gomail.SetCopyFunc(func(writer io.Writer) error { + bytes, err := fileBackend.ReadFile(fileInfo.Path) + if err != nil { + return err + } + if _, err := writer.Write(bytes); err != nil { + return model.NewAppError("SendMail", "utils.mail.sendMail.attachments.write_error", nil, err.Error(), http.StatusInternalServerError) + } + return nil + })) + + } + + } + conn, err1 := connectToSMTPServer(config) if err1 != nil { return err1 @@ -147,11 +185,11 @@ func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *mo defer c.Quit() defer c.Close() - if err := c.Mail(fromMail.Address); err != nil { + if err := c.Mail(from.Address); err != nil { return model.NewAppError("SendMail", "utils.mail.send_mail.from_address.app_error", nil, err.Error(), http.StatusInternalServerError) } - if err := c.Rcpt(to); err != nil { + if err := c.Rcpt(smtpTo); err != nil { return model.NewAppError("SendMail", "utils.mail.send_mail.to_address.app_error", nil, err.Error(), http.StatusInternalServerError) } diff --git a/utils/mail_test.go b/utils/mail_test.go index 574f71f469..207fe32a53 100644 --- a/utils/mail_test.go +++ b/utils/mail_test.go @@ -7,6 +7,10 @@ import ( "strings" "testing" + "net/mail" + + "github.com/mattermost/mattermost-server/model" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -39,9 +43,9 @@ func TestSendMailUsingConfig(t *testing.T) { require.Nil(t, err) T = GetUserTranslations("en") - var emailTo string = "test@example.com" - var emailSubject string = "Testing this email" - var emailBody string = "This is a test from autobot" + var emailTo = "test@example.com" + var emailSubject = "Testing this email" + var emailBody = "This is a test from autobot" //Delete all the messages before check the sample email DeleteMailBox(emailTo) @@ -50,7 +54,7 @@ func TestSendMailUsingConfig(t *testing.T) { t.Log(err) t.Fatal("Should connect to the STMP Server") } else { - //Check if the email was send to the rigth email address + //Check if the email was send to the right email address var resultsMailbox JSONMessageHeaderInbucket err := RetryInbucket(5, func() error { var err error @@ -75,3 +79,78 @@ func TestSendMailUsingConfig(t *testing.T) { } } } + +func TestSendMailUsingConfigAdvanced(t *testing.T) { + cfg, _, err := LoadConfig("config.json") + require.Nil(t, err) + T = GetUserTranslations("en") + + var mimeTo = "test@example.com" + var smtpTo = "test2@example.com" + var from = mail.Address{Name: "Nobody", Address: "nobody@mattermost.com"} + var emailSubject = "Testing this email" + var emailBody = "This is a test from autobot" + + //Delete all the messages before check the sample email + DeleteMailBox(smtpTo) + + // create a file that will be attached to the email + fileBackend, err := NewFileBackend(&cfg.FileSettings) + assert.Nil(t, err) + fileContents := []byte("hello world") + fileName := "file.txt" + assert.Nil(t, fileBackend.WriteFile(fileContents, fileName)) + defer fileBackend.RemoveFile(fileName) + + attachments := make([]*model.FileInfo, 1) + attachments[0] = &model.FileInfo{ + Name: fileName, + Path: fileName, + } + + headers := make(map[string]string) + headers["TestHeader"] = "TestValue" + + if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg); err != nil { + t.Log(err) + t.Fatal("Should connect to the STMP Server") + } else { + //Check if the email was send to the right email address + var resultsMailbox JSONMessageHeaderInbucket + err := RetryInbucket(5, func() error { + var err error + resultsMailbox, err = GetMailBox(smtpTo) + return err + }) + if err != nil { + t.Log(err) + t.Fatal("No emails found for address " + smtpTo) + } + if err == nil && len(resultsMailbox) > 0 { + if !strings.ContainsAny(resultsMailbox[0].To[0], smtpTo) { + t.Fatal("Wrong To recipient") + } else { + if resultsEmail, err := GetMessageFromMailbox(smtpTo, resultsMailbox[0].ID); err == nil { + if !strings.Contains(resultsEmail.Body.Text, emailBody) { + t.Log(resultsEmail.Body.Text) + t.Fatal("Received message") + } + + // verify that the To header of the email message is set to the MIME recipient, even though we got it out of the SMTP recipient's email inbox + assert.Equal(t, mimeTo, resultsEmail.Header["To"][0]) + + // verify that the MIME from address is correct - unfortunately, we can't verify the SMTP from address + assert.Equal(t, from.String(), resultsEmail.Header["From"][0]) + + // check that the custom mime headers came through - header case seems to get mutated + assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0]) + + // ensure that the attachment was successfully sent + assert.Len(t, resultsEmail.Attachments, 1) + assert.Equal(t, fileName, resultsEmail.Attachments[0].Filename) + assert.Equal(t, fileContents, resultsEmail.Attachments[0].Bytes) + } + } + } + } +} From 7bd298ceaa24c0721e0acd65692cb2d1ca4983f3 Mon Sep 17 00:00:00 2001 From: Vordimous Date: Wed, 7 Feb 2018 09:17:18 -0500 Subject: [PATCH 05/14] PLT-7537: Move channel CLI command posts system message to channel. (#8161) * [PTL-7537] implement feature and test * [PTL-7537] Update feature to post the the room requiring a username flag to be used * [PTL-7537] update tests with username * update test to remove changes to the test helper struct * use the basic team and user --- app/channel.go | 30 ++++++++++++++++++++++++++++-- app/channel_test.go | 4 ++-- cmd/platform/channel.go | 17 +++++++++++++---- cmd/platform/channel_test.go | 27 +++++++++++++++++++++++++++ i18n/en.json | 8 ++++++++ model/post.go | 2 ++ 6 files changed, 80 insertions(+), 8 deletions(-) diff --git a/app/channel.go b/app/channel.go index e4bf486547..8ac1f421c3 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1359,7 +1359,7 @@ func (a *App) PermanentDeleteChannel(channel *model.Channel) *model.AppError { // This function is intended for use from the CLI. It is not robust against people joining the channel while the move // is in progress, and therefore should not be used from the API without first fixing this potential race condition. -func (a *App) MoveChannel(team *model.Team, channel *model.Channel) *model.AppError { +func (a *App) MoveChannel(team *model.Team, channel *model.Channel, user *model.User) *model.AppError { // Check that all channel members are in the destination team. if channelMembers, err := a.GetChannelMembersPage(channel.Id, 0, 10000000); err != nil { return err @@ -1378,11 +1378,37 @@ func (a *App) MoveChannel(team *model.Team, channel *model.Channel) *model.AppEr } } - // Change the Team ID of the channel. + // keep instance of the previous team + var previousTeam *model.Team + if result := <-a.Srv.Store.Team().Get(channel.TeamId); result.Err != nil { + return result.Err + } else { + previousTeam = result.Data.(*model.Team) + } channel.TeamId = team.Id if result := <-a.Srv.Store.Channel().Update(channel); result.Err != nil { return result.Err } + a.postChannelMoveMessage(user, channel, previousTeam) + + return nil +} + +func (a *App) postChannelMoveMessage(user *model.User, channel *model.Channel, previousTeam *model.Team) *model.AppError { + + post := &model.Post{ + ChannelId: channel.Id, + Message: fmt.Sprintf(utils.T("api.team.move_channel.success"), previousTeam.Name), + Type: model.POST_MOVE_CHANNEL, + UserId: user.Id, + Props: model.StringInterface{ + "username": user.Username, + }, + } + + if _, err := a.CreatePost(post, channel, false); err != nil { + return model.NewAppError("postChannelMoveMessage", "api.team.move_channel.post.error", nil, err.Error(), http.StatusInternalServerError) + } return nil } diff --git a/app/channel_test.go b/app/channel_test.go index a414fbb35c..d315fbae6f 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -97,7 +97,7 @@ func TestMoveChannel(t *testing.T) { t.Fatal(err) } - if err := th.App.MoveChannel(targetTeam, channel1); err == nil { + if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err == nil { t.Fatal("Should have failed due to mismatched members.") } @@ -105,7 +105,7 @@ func TestMoveChannel(t *testing.T) { t.Fatal(err) } - if err := th.App.MoveChannel(targetTeam, channel1); err != nil { + if err := th.App.MoveChannel(targetTeam, channel1, th.BasicUser); err != nil { t.Fatal(err) } } diff --git a/cmd/platform/channel.go b/cmd/platform/channel.go index 98bdcebb8c..5d86ad9dab 100644 --- a/cmd/platform/channel.go +++ b/cmd/platform/channel.go @@ -106,6 +106,8 @@ func init() { channelCreateCmd.Flags().String("purpose", "", "Channel purpose") channelCreateCmd.Flags().Bool("private", false, "Create a private channel.") + moveChannelsCmd.Flags().String("username", "", "Required. Username who is moving the channel.") + deleteChannelsCmd.Flags().Bool("confirm", false, "Confirm you really want to delete the channels.") modifyChannelCmd.Flags().Bool("private", false, "Convert the channel to a private channel") @@ -319,26 +321,33 @@ func moveChannelsCmdF(cmd *cobra.Command, args []string) error { return errors.New("Unable to find destination team '" + args[0] + "'") } + username, erru := cmd.Flags().GetString("username") + if erru != nil || username == "" { + return errors.New("Username is required") + } + user := getUserFromUserArg(a, username) + channels := getChannelsFromChannelArgs(a, args[1:]) for i, channel := range channels { if channel == nil { CommandPrintErrorln("Unable to find channel '" + args[i] + "'") continue } - if err := moveChannel(a, team, channel); err != nil { + originTeamID := channel.TeamId + if err := moveChannel(a, team, channel, user); err != nil { CommandPrintErrorln("Unable to move channel '" + channel.Name + "' error: " + err.Error()) } else { - CommandPrettyPrintln("Moved channel '" + channel.Name + "'") + CommandPrettyPrintln("Moved channel '" + channel.Name + "' to " + team.Name + "(" + team.Id + ") from " + originTeamID + ".") } } return nil } -func moveChannel(a *app.App, team *model.Team, channel *model.Channel) *model.AppError { +func moveChannel(a *app.App, team *model.Team, channel *model.Channel, user *model.User) *model.AppError { oldTeamId := channel.TeamId - if err := a.MoveChannel(team, channel); err != nil { + if err := a.MoveChannel(team, channel, user); err != nil { return err } diff --git a/cmd/platform/channel_test.go b/cmd/platform/channel_test.go index 1e69156798..cf8603cf3a 100644 --- a/cmd/platform/channel_test.go +++ b/cmd/platform/channel_test.go @@ -44,6 +44,33 @@ func TestRemoveChannel(t *testing.T) { checkCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) } +func TestMoveChannel(t *testing.T) { + th := api.Setup().InitBasic() + defer th.TearDown() + + client := th.BasicClient + team1 := th.BasicTeam + team2 := th.CreateTeam(client) + user1 := th.BasicUser + th.LinkUserToTeam(user1, team2) + channel := th.BasicChannel + + th.LinkUserToTeam(user1, team1) + th.LinkUserToTeam(user1, team2) + + adminEmail := user1.Email + adminUsername := user1.Username + origin := team1.Name + ":" + channel.Name + dest := team2.Name + + checkCommand(t, "channel", "add", origin, adminEmail) + + // should fail with nill because errors are logged instead of returned when a channel does not exist + require.Nil(t, runCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername)) + + checkCommand(t, "channel", "move", dest, origin, "--username", adminUsername) +} + func TestListChannels(t *testing.T) { th := api.Setup().InitBasic() defer th.TearDown() diff --git a/i18n/en.json b/i18n/en.json index 1a04aeeed2..21f0661cc6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3130,6 +3130,14 @@ "id": "app.channel.move_channel.members_do_not_match.error", "translation": "Cannot move a channel unless all its members are already members of the destination team." }, + { + "id": "api.team.move_channel.success", + "translation": "This channel has been moved to this team from %v." + }, + { + "id": "api.team.move_channel.post.error", + "translation": "Failed to post channel move message." + }, { "id": "app.channel.post_update_channel_purpose_message.post.error", "translation": "Failed to post channel purpose message" diff --git a/model/post.go b/model/post.go index 391b948f41..7cf0f1b35b 100644 --- a/model/post.go +++ b/model/post.go @@ -28,6 +28,7 @@ const ( POST_ADD_REMOVE = "system_add_remove" // Deprecated, use POST_ADD_TO_CHANNEL or POST_REMOVE_FROM_CHANNEL instead POST_ADD_TO_CHANNEL = "system_add_to_channel" POST_REMOVE_FROM_CHANNEL = "system_remove_from_channel" + POST_MOVE_CHANNEL = "system_move_channel" POST_ADD_TO_TEAM = "system_add_to_team" POST_REMOVE_FROM_TEAM = "system_remove_from_team" POST_HEADER_CHANGE = "system_header_change" @@ -196,6 +197,7 @@ func (o *Post) IsValid() *AppError { POST_LEAVE_TEAM, POST_ADD_TO_CHANNEL, POST_REMOVE_FROM_CHANNEL, + POST_MOVE_CHANNEL, POST_ADD_TO_TEAM, POST_REMOVE_FROM_TEAM, POST_SLACK_ATTACHMENT, From eff65aa05c74e93533c2504b8141b0474011e68c Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 7 Feb 2018 11:05:46 -0600 Subject: [PATCH 06/14] ABC-132: sign error page parameters (#8197) * sign error page parameters * add comments --- api/api.go | 6 +++- api/context.go | 6 ++-- api/file.go | 4 +-- api4/file.go | 4 +-- api4/oauth.go | 24 ++++++++----- app/app.go | 22 +++++++----- app/config.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++ app/config_test.go | 9 +++++ model/system.go | 23 +++++++++--- utils/api.go | 27 ++++++++++---- utils/api_test.go | 49 ++++++++++++++++++++++++++ web/web.go | 2 +- 12 files changed, 227 insertions(+), 37 deletions(-) create mode 100644 utils/api_test.go diff --git a/api/api.go b/api/api.go index 2d65bb2166..70f36db857 100644 --- a/api/api.go +++ b/api/api.go @@ -109,7 +109,7 @@ func Init(a *app.App, root *mux.Router) *API { api.InitReaction() // 404 on any api route before web.go has a chance to serve it - root.Handle("/api/{anything:.*}", http.HandlerFunc(Handle404)) + root.Handle("/api/{anything:.*}", http.HandlerFunc(api.Handle404)) a.InitEmailBatching() @@ -120,6 +120,10 @@ func Init(a *app.App, root *mux.Router) *API { return api } +func (api *API) Handle404(w http.ResponseWriter, r *http.Request) { + Handle404(api.App, w, r) +} + func ReturnStatusOK(w http.ResponseWriter) { m := make(map[string]string) m[model.STATUS] = model.STATUS_OK diff --git a/api/context.go b/api/context.go index b28a247317..a8ff2b6940 100644 --- a/api/context.go +++ b/api/context.go @@ -229,7 +229,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if c.Err.StatusCode == http.StatusUnauthorized { http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) } else { - utils.RenderWebError(c.Err, w, r) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) } } @@ -434,7 +434,7 @@ func IsApiCall(r *http.Request) bool { return strings.Index(r.URL.Path, "/api/") == 0 } -func Handle404(w http.ResponseWriter, r *http.Request) { +func Handle404(a *app.App, w http.ResponseWriter, r *http.Request) { err := model.NewAppError("Handle404", "api.context.404.app_error", nil, "", http.StatusNotFound) l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) @@ -444,7 +444,7 @@ func Handle404(w http.ResponseWriter, r *http.Request) { err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" w.Write([]byte(err.ToJson())) } else { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } } diff --git a/api/file.go b/api/file.go index 2d626304e1..3b8984816d 100644 --- a/api/file.go +++ b/api/file.go @@ -174,12 +174,12 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { if hash != correctHash { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } } else { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } diff --git a/api4/file.go b/api4/file.go index 48ee281fe2..acc4c78e58 100644 --- a/api4/file.go +++ b/api4/file.go @@ -281,13 +281,13 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { if len(hash) == 0 { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } if hash != app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt) { c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey()) return } diff --git a/api4/oauth.go b/api4/oauth.go index 655adaaeee..d0f43256a8 100644 --- a/api4/oauth.go +++ b/api4/oauth.go @@ -313,7 +313,7 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { if !c.App.Config().ServiceSettings.EnableOAuthServiceProvider { err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -326,13 +326,13 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { } if err := authRequest.IsValid(); err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId) if err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -343,7 +343,8 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { } if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) { - utils.RenderWebError(model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest), w, r) + err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -360,7 +361,7 @@ func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest) if err != nil { - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) return } @@ -441,7 +442,10 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if len(code) == 0 { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?type=oauth_missing_code&service="+strings.Title(service), http.StatusTemporaryRedirect) + utils.RenderWebError(w, r, http.StatusTemporaryRedirect, url.Values{ + "type": []string{"oauth_missing_code"}, + "service": []string{strings.Title(service)}, + }, c.App.AsymmetricSigningKey()) return } @@ -462,7 +466,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) } return } @@ -474,7 +478,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { if action == model.OAUTH_ACTION_MOBILE { w.Write([]byte(err.ToJson())) } else { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(err.Message), http.StatusTemporaryRedirect) + utils.RenderWebAppError(w, r, err, c.App.AsymmetricSigningKey()) } return } @@ -559,7 +563,9 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } if !c.App.Config().TeamSettings.EnableUserCreation { - http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+url.QueryEscape(utils.T("api.oauth.singup_with_oauth.disabled.app_error")), http.StatusTemporaryRedirect) + utils.RenderWebError(w, r, http.StatusBadRequest, url.Values{ + "message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")}, + }, c.App.AsymmetricSigningKey()) return } diff --git a/app/app.go b/app/app.go index 1e46d29d07..0b5efa76b3 100644 --- a/app/app.go +++ b/app/app.go @@ -4,6 +4,7 @@ package app import ( + "crypto/ecdsa" "html/template" "net" "net/http" @@ -60,13 +61,14 @@ type App struct { newStore func() store.Store - htmlTemplateWatcher *utils.HTMLTemplateWatcher - sessionCache *utils.Cache - roles map[string]*model.Role - configListenerId string - licenseListenerId string - disableConfigWatch bool - configWatcher *utils.ConfigWatcher + htmlTemplateWatcher *utils.HTMLTemplateWatcher + sessionCache *utils.Cache + roles map[string]*model.Role + configListenerId string + licenseListenerId string + disableConfigWatch bool + configWatcher *utils.ConfigWatcher + asymmetricSigningKey *ecdsa.PrivateKey pluginCommands []*PluginCommand pluginCommandsLock sync.RWMutex @@ -139,6 +141,10 @@ func New(options ...Option) (*App, error) { } app.Srv.Store = app.newStore() + if err := app.ensureAsymmetricSigningKey(); err != nil { + return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key") + } + app.initJobs() app.initBuiltInPlugins() @@ -448,5 +454,5 @@ func (a *App) Handle404(w http.ResponseWriter, r *http.Request) { l4g.Debug("%v: code=404 ip=%v", r.URL.Path, utils.GetIpAddress(r)) - utils.RenderWebError(err, w, r) + utils.RenderWebAppError(w, r, err, a.AsymmetricSigningKey()) } diff --git a/app/config.go b/app/config.go index a2398f9e91..46426c442a 100644 --- a/app/config.go +++ b/app/config.go @@ -4,7 +4,12 @@ package app import ( + "crypto/ecdsa" + "crypto/elliptic" "crypto/md5" + "crypto/rand" + "crypto/x509" + "encoding/base64" "encoding/json" "fmt" "runtime/debug" @@ -116,8 +121,91 @@ 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 { + return nil + } + + var key *model.SystemAsymmetricSigningKey + + result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) + if result.Err == nil { + if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil { + return err + } + } + + // If we don't already have a key, try to generate one. + if key == nil { + newECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + newKey := &model.SystemAsymmetricSigningKey{ + ECDSAKey: &model.SystemECDSAKey{ + Curve: "P-256", + X: newECDSAKey.X, + Y: newECDSAKey.Y, + D: newECDSAKey.D, + }, + } + system := &model.System{ + Name: model.SYSTEM_ASYMMETRIC_SIGNING_KEY, + } + v, err := json.Marshal(newKey) + if err != nil { + return err + } + system.Value = string(v) + if result = <-a.Srv.Store.System().Save(system); result.Err == nil { + // If we were able to save the key, use it, otherwise ignore the error. + key = newKey + } + } + + // If we weren't able to save a new key above, another server must have beat us to it. Get the + // key from the database, and if that fails, error out. + if key == nil { + result := <-a.Srv.Store.System().GetByName(model.SYSTEM_ASYMMETRIC_SIGNING_KEY) + if result.Err != nil { + return result.Err + } else if err := json.Unmarshal([]byte(result.Data.(*model.System).Value), &key); err != nil { + return err + } + } + + var curve elliptic.Curve + switch key.ECDSAKey.Curve { + case "P-256": + curve = elliptic.P256() + default: + return fmt.Errorf("unknown curve: " + key.ECDSAKey.Curve) + } + a.asymmetricSigningKey = &ecdsa.PrivateKey{ + PublicKey: ecdsa.PublicKey{ + Curve: curve, + X: key.ECDSAKey.X, + Y: key.ECDSAKey.Y, + }, + D: key.ECDSAKey.D, + } + a.regenerateClientConfig() + return nil +} + +// AsymmetricSigningKey will return a private key that can be used for asymmetric signing. +func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey { + return a.asymmetricSigningKey +} + func (a *App) regenerateClientConfig() { a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId()) + if key := a.AsymmetricSigningKey(); key != nil { + der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey) + a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) + } clientConfigJSON, _ := json.Marshal(a.clientConfig) a.clientConfigHash = fmt.Sprintf("%x", md5.Sum(clientConfigJSON)) } diff --git a/app/config_test.go b/app/config_test.go index e3d50b9583..5ee999f0fc 100644 --- a/app/config_test.go +++ b/app/config_test.go @@ -6,6 +6,8 @@ package app import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/mattermost/mattermost-server/model" ) @@ -54,3 +56,10 @@ func TestConfigListener(t *testing.T) { t.Fatal("listener 2 should've been called") } } + +func TestAsymmetricSigningKey(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + assert.NotNil(t, th.App.AsymmetricSigningKey()) + assert.NotEmpty(t, th.App.ClientConfig()["AsymmetricSigningPublicKey"]) +} diff --git a/model/system.go b/model/system.go index 020c50858f..2a636b14fc 100644 --- a/model/system.go +++ b/model/system.go @@ -6,14 +6,16 @@ package model import ( "encoding/json" "io" + "math/big" ) const ( - SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" - SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" - SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" - SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" - SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" + SYSTEM_DIAGNOSTIC_ID = "DiagnosticId" + SYSTEM_RAN_UNIT_TESTS = "RanUnitTests" + SYSTEM_LAST_SECURITY_TIME = "LastSecurityTime" + SYSTEM_ACTIVE_LICENSE_ID = "ActiveLicenseId" + SYSTEM_LAST_COMPLIANCE_TIME = "LastComplianceTime" + SYSTEM_ASYMMETRIC_SIGNING_KEY = "AsymmetricSigningKey" ) type System struct { @@ -31,3 +33,14 @@ func SystemFromJson(data io.Reader) *System { json.NewDecoder(data).Decode(&o) return o } + +type SystemAsymmetricSigningKey struct { + ECDSAKey *SystemECDSAKey `json:"ecdsa_key,omitempty"` +} + +type SystemECDSAKey struct { + Curve string `json:"curve"` + X *big.Int `json:"x"` + Y *big.Int `json:"y"` + D *big.Int `json:"d,omitempty"` +} diff --git a/utils/api.go b/utils/api.go index 005c3284bf..51524074d7 100644 --- a/utils/api.go +++ b/utils/api.go @@ -4,6 +4,9 @@ package utils import ( + "crypto" + "crypto/rand" + "encoding/base64" "fmt" "html/template" "net/http" @@ -32,13 +35,25 @@ func OriginChecker(allowedOrigins string) func(*http.Request) bool { } } -func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) { - status := http.StatusTemporaryRedirect - if err.StatusCode != http.StatusInternalServerError { - status = err.StatusCode - } +func RenderWebAppError(w http.ResponseWriter, r *http.Request, err *model.AppError, s crypto.Signer) { + RenderWebError(w, r, err.StatusCode, url.Values{ + "message": []string{err.Message}, + }, s) +} + +func RenderWebError(w http.ResponseWriter, r *http.Request, status int, params url.Values, s crypto.Signer) { + queryString := params.Encode() + + h := crypto.SHA256 + sum := h.New() + sum.Write([]byte("/error?" + queryString)) + signature, err := s.Sign(rand.Reader, sum.Sum(nil), h) + if err != nil { + http.Error(w, "", http.StatusInternalServerError) + return + } + destination := strings.TrimRight(GetSiteURL(), "/") + "/error?" + queryString + "&s=" + base64.URLEncoding.EncodeToString(signature) - destination := strings.TrimRight(GetSiteURL(), "/") + "/error?message=" + url.QueryEscape(err.Message) if status >= 300 && status < 400 { http.Redirect(w, r, destination, status) return diff --git a/utils/api_test.go b/utils/api_test.go new file mode 100644 index 0000000000..5e41c7bfef --- /dev/null +++ b/utils/api_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package utils + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/asn1" + "encoding/base64" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenderWebError(t *testing.T) { + r := httptest.NewRequest("GET", "http://foo", nil) + w := httptest.NewRecorder() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + RenderWebError(w, r, http.StatusTemporaryRedirect, url.Values{ + "foo": []string{"bar"}, + }, key) + + resp := w.Result() + location, err := url.Parse(resp.Header.Get("Location")) + require.NoError(t, err) + require.NotEmpty(t, location.Query().Get("s")) + + type ecdsaSignature struct { + R, S *big.Int + } + var rs ecdsaSignature + s, err := base64.URLEncoding.DecodeString(location.Query().Get("s")) + require.NoError(t, err) + _, err = asn1.Unmarshal(s, &rs) + require.NoError(t, err) + + assert.Equal(t, "bar", location.Query().Get("foo")) + h := sha256.Sum256([]byte("/error?foo=bar")) + assert.True(t, ecdsa.Verify(&key.PublicKey, h[:], rs.R, rs.S)) +} diff --git a/web/web.go b/web/web.go index 321d83a756..e0edd1b7a9 100644 --- a/web/web.go +++ b/web/web.go @@ -94,7 +94,7 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { } if api.IsApiCall(r) { - api.Handle404(w, r) + api.Handle404(c.App, w, r) return } From 0f703a3368a0b16fcd48b474377f0dbd2144f366 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 7 Feb 2018 16:20:51 -0600 Subject: [PATCH 07/14] Eliminate utils.SetLicense calls (#8217) * eliminate utils.SetLicense calls * test fix * another test fix * more test fixes --- api/channel_test.go | 245 ++++--------------------------------------- api/post_test.go | 42 +------- api/team_test.go | 42 +------- api/user_test.go | 15 +-- api/webhook_test.go | 38 +------ api4/channel_test.go | 148 +++----------------------- api4/post_test.go | 43 +------- api4/team_test.go | 71 ++----------- api4/user_test.go | 53 +--------- app/app.go | 3 + app/config.go | 8 -- app/license.go | 16 ++- app/session_test.go | 13 +-- model/license.go | 19 ++++ 14 files changed, 91 insertions(+), 665 deletions(-) diff --git a/api/channel_test.go b/api/channel_test.go index 5533105c3d..9268d90717 100644 --- a/api/channel_test.go +++ b/api/channel_test.go @@ -12,7 +12,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -97,23 +96,9 @@ func TestCreateChannel(t *testing.T) { t.Fatal("Should have errored out on direct channel type") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) channel2 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel3 := &model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -130,7 +115,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic2() channel2.Name = "zz" + model.NewId() + "a" @@ -160,7 +144,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel2.Name = "zz" + model.NewId() + "a" channel3.Name = "zz" + model.NewId() + "a" @@ -181,9 +164,7 @@ func TestCreateChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel4 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel5 := model.Channel{DisplayName: "Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -196,7 +177,6 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestCreateDirectChannel(t *testing.T) { @@ -367,23 +347,9 @@ func TestUpdateChannel(t *testing.T) { t.Fatal("should have failed - channel deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) channel2 := th.CreateChannel(Client, team) channel3 := th.CreatePrivateChannel(Client, team) @@ -404,14 +370,8 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -447,14 +407,9 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.UpdateChannel(channel2); err == nil { t.Fatal("should have errored not team admin") @@ -477,14 +432,8 @@ func TestUpdateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannel(channel2); err == nil { t.Fatal("should have errored not system admin") @@ -503,9 +452,7 @@ func TestUpdateChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := Client.UpdateChannel(channel2); err != nil { t.Fatal(err) @@ -618,23 +565,9 @@ func TestUpdateChannelHeader(t *testing.T) { t.Fatal("should have errored non-channel member trying to update header") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -659,11 +592,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -688,11 +618,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelHeader(data2); err == nil { t.Fatal("should have errored not team admin") @@ -715,11 +642,8 @@ func TestUpdateChannelHeader(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelHeader(data2); err == nil { t.Fatal("should have errored not system admin") @@ -741,9 +665,7 @@ func TestUpdateChannelHeader(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { t.Fatal(err) @@ -814,23 +736,9 @@ func TestUpdateChannelPurpose(t *testing.T) { t.Fatal("should have errored non-channel member trying to update purpose") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() channel2 := th.CreateChannel(Client, team) @@ -855,11 +763,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.MakeUserChannelUser(th.BasicUser, channel2) th.MakeUserChannelUser(th.BasicUser, channel3) sqlstore.ClearChannelCaches() @@ -884,11 +789,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelPurpose(data2); err == nil { t.Fatal("should have errored not team admin") @@ -911,11 +813,8 @@ func TestUpdateChannelPurpose(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.UpdateChannelPurpose(data2); err == nil { t.Fatal("should have errored not system admin") @@ -937,9 +836,7 @@ func TestUpdateChannelPurpose(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) if _, err := SystemAdminClient.UpdateChannelHeader(data2); err != nil { t.Fatal(err) } @@ -1400,23 +1297,9 @@ func TestDeleteChannel(t *testing.T) { t.Fatal("should have failed - channel already deleted") } - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginSystemAdmin() th.LinkUserToTeam(th.BasicUser, team) @@ -1438,16 +1321,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1498,16 +1375,11 @@ func TestDeleteChannel(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1537,16 +1409,10 @@ func TestDeleteChannel(t *testing.T) { t.Fatal(err) } - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginSystemAdmin() @@ -1578,9 +1444,7 @@ func TestDeleteChannel(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel2 = th.CreateChannel(Client, team) channel3 = th.CreatePrivateChannel(Client, team) @@ -1595,10 +1459,6 @@ func TestDeleteChannel(t *testing.T) { if _, err := Client.DeleteChannel(channel3.Id); err != nil { t.Fatal(err) } - - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_ALL }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_ALL }) - th.App.SetDefaultRolesBasedOnConfig() } func TestGetChannelStats(t *testing.T) { @@ -1675,16 +1535,9 @@ func TestAddChannelMember(t *testing.T) { } // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) @@ -1694,18 +1547,8 @@ func TestAddChannelMember(t *testing.T) { } // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can add other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1719,10 +1562,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1733,10 +1572,7 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.AddChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) @@ -1746,10 +1582,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) @@ -1760,10 +1592,7 @@ func TestAddChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.AddChannelMember(channel6.Id, user2.Id); err != nil { t.Fatal(err) @@ -1773,10 +1602,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) @@ -1860,16 +1685,9 @@ func TestRemoveChannelMember(t *testing.T) { th.LoginBasic() // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() channel3 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel3 = Client.Must(th.SystemAdminClient.CreateChannel(channel3)).Data.(*model.Channel) @@ -1880,18 +1698,8 @@ func TestRemoveChannelMember(t *testing.T) { } // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can remove other users. channel4 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} @@ -1906,10 +1714,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel5 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel5 = Client.Must(th.SystemAdminClient.CreateChannel(channel5)).Data.(*model.Channel) @@ -1921,9 +1725,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, channel5) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.RemoveChannelMember(channel5.Id, user2.Id); err != nil { t.Fatal(err) @@ -1933,10 +1735,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel6 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel6 = Client.Must(th.SystemAdminClient.CreateChannel(channel6)).Data.(*model.Channel) @@ -1948,10 +1746,7 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.RemoveChannelMember(channel6.Id, user2.Id); err != nil { t.Fatal(err) @@ -1961,10 +1756,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() channel7 := &model.Channel{DisplayName: "A Test API Name", Name: "zz" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id} channel7 = Client.Must(th.SystemAdminClient.CreateChannel(channel7)).Data.(*model.Channel) diff --git a/api/post_test.go b/api/post_test.go index 299fdf0462..2fc79d9b12 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -160,20 +160,8 @@ func TestCreatePost(t *testing.T) { } } - isLicensed := utils.IsLicensed() - license := utils.License() - disableTownSquareReadOnly := th.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) defaultChannel := store.Must(th.App.Srv.Store.Channel().GetByName(team.Id, model.DEFAULT_CHANNEL, true)).(*model.Channel) defaultPost := &model.Post{ @@ -466,16 +454,7 @@ func TestUpdatePost(t *testing.T) { } // Test licensed policy controls for edit post - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_NEVER }) post4 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} @@ -966,7 +945,6 @@ func TestDeletePosts(t *testing.T) { team1 := th.BasicTeam th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_ALL }) - th.App.SetDefaultRolesBasedOnConfig() time.Sleep(10 * time.Millisecond) post1 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} @@ -1021,15 +999,7 @@ func TestDeletePosts(t *testing.T) { } // Test licensed policy controls for delete post - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.UpdateUserToTeamAdmin(th.BasicUser2, th.BasicTeam) @@ -1044,7 +1014,6 @@ func TestDeletePosts(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -1069,7 +1038,6 @@ func TestDeletePosts(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.RestrictPostDelete = model.PERMISSIONS_DELETE_POST_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -1088,9 +1056,7 @@ func TestDeletePosts(t *testing.T) { } // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) time.Sleep(10 * time.Millisecond) post7 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a"} diff --git a/api/team_test.go b/api/team_test.go index b1c8925445..696cf31bbd 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -139,20 +139,8 @@ func TestAddUserToTeam(t *testing.T) { t.Fatal(err) } - // Restore config/license at end of test case. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Test without the EE license to see that the permission restriction is ignored. user3 := th.CreateUser(th.BasicClient) @@ -161,10 +149,7 @@ func TestAddUserToTeam(t *testing.T) { } // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular user can't add someone to the team. user4 := th.CreateUser(th.BasicClient) @@ -175,11 +160,8 @@ func TestAddUserToTeam(t *testing.T) { // Should work as team admin. th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() user5 := th.CreateUser(th.BasicClient) if _, err := th.BasicClient.AddUserToTeam(th.BasicTeam.Id, user5.Id); err != nil { @@ -188,7 +170,6 @@ func TestAddUserToTeam(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. user6 := th.CreateUser(th.BasicClient) @@ -565,13 +546,7 @@ func TestInviteMembers(t *testing.T) { t.Fatal("Should have errored out on no invites to send") } - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic2() th.LinkUserToTeam(th.BasicUser2, team) @@ -580,17 +555,7 @@ func TestInviteMembers(t *testing.T) { t.Fatal(err) } - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not team admin and licensed") @@ -606,7 +571,6 @@ func TestInviteMembers(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.InviteMembers(invites); err == nil { t.Fatal("should have errored not system admin and licensed") diff --git a/api/user_test.go b/api/user_test.go index 8d6aad22b6..f65d7c45b7 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -1889,17 +1889,7 @@ func TestUpdateMfa(t *testing.T) { Client := th.BasicClient - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - }() - utils.SetIsLicensed(false) - utils.SetLicense(&model.License{Features: &model.Features{}}) - if utils.License().Features.MFA == nil { - utils.License().Features.MFA = new(bool) - } + th.App.SetLicense(nil) team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} rteam, _ := Client.CreateTeam(&team) @@ -1925,8 +1915,7 @@ func TestUpdateMfa(t *testing.T) { t.Fatal("should have failed - not licensed") } - utils.SetIsLicensed(true) - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) if _, err := Client.UpdateMfa(true, "123456"); err == nil { diff --git a/api/webhook_test.go b/api/webhook_test.go index b5a8366037..f4d46496bc 100644 --- a/api/webhook_test.go +++ b/api/webhook_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateIncomingHook(t *testing.T) { @@ -93,7 +92,6 @@ func TestCreateIncomingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.CreateIncomingWebhook(hook); err != nil { t.Fatal(err) @@ -133,7 +131,6 @@ func TestUpdateIncomingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := createIncomingWebhook(channel1.Id, Client, t) @@ -217,7 +214,6 @@ func TestUpdateIncomingHook(t *testing.T) { t.Run("OnlyAdminIntegrationsDisabled", func(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() t.Run("UpdateHookOfSameUser", func(t *testing.T) { sameUserHook := &model.IncomingWebhook{ChannelId: channel1.Id, UserId: user2.Id} @@ -240,7 +236,6 @@ func TestUpdateIncomingHook(t *testing.T) { }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() Client.Logout() th.UpdateUserToTeamAdmin(user2, team) @@ -324,7 +319,6 @@ func TestListIncomingHooks(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook1 := &model.IncomingWebhook{ChannelId: channel1.Id} hook1 = Client.Must(Client.CreateIncomingWebhook(hook1)).Data.(*model.IncomingWebhook) @@ -351,7 +345,6 @@ func TestListIncomingHooks(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.ListIncomingWebhooks(); err != nil { t.Fatal(err) @@ -376,7 +369,6 @@ func TestDeleteIncomingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.IncomingWebhook{ChannelId: channel1.Id} hook = Client.Must(Client.CreateIncomingWebhook(hook)).Data.(*model.IncomingWebhook) @@ -410,7 +402,6 @@ func TestDeleteIncomingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.DeleteIncomingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -447,7 +438,6 @@ func TestCreateOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} @@ -518,7 +508,6 @@ func TestCreateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.CreateOutgoingWebhook(hook); err != nil { t.Fatal(err) @@ -551,7 +540,6 @@ func TestListOutgoingHooks(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook1 := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook1 = Client.Must(Client.CreateOutgoingWebhook(hook1)).Data.(*model.OutgoingWebhook) @@ -578,7 +566,6 @@ func TestListOutgoingHooks(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.ListOutgoingWebhooks(); err != nil { t.Fatal(err) @@ -609,7 +596,6 @@ func TestUpdateOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"cats"}, Client, t) createOutgoingWebhook(channel1.Id, []string{"http://nowhere.com"}, []string{"dogs"}, Client, t) @@ -683,7 +669,6 @@ func TestUpdateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() hook2 := createOutgoingWebhook(channel1.Id, []string{"http://nowhereelse.com"}, []string{"dogs"}, Client, t) if _, err := Client.UpdateOutgoingWebhook(hook2); err != nil { @@ -691,7 +676,6 @@ func TestUpdateOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() Client.Logout() th.LinkUserToTeam(user3, team) @@ -779,7 +763,6 @@ func TestDeleteOutgoingHook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -813,7 +796,6 @@ func TestDeleteOutgoingHook(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() if _, err := Client.DeleteOutgoingWebhook(hook.Id); err == nil { t.Fatal("should have failed - not creator or team admin") @@ -848,7 +830,6 @@ func TestRegenOutgoingHookToken(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() hook := &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -883,7 +864,6 @@ func TestRegenOutgoingHookToken(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() hook = &model.OutgoingWebhook{ChannelId: channel1.Id, CallbackURLs: []string{"http://nowhere.com"}} hook = Client.Must(Client.CreateOutgoingWebhook(hook)).Data.(*model.OutgoingWebhook) @@ -917,10 +897,6 @@ func TestIncomingWebhooks(t *testing.T) { user2 := th.CreateUser(Client) th.LinkUserToTeam(user2, team) - enableIncomingHooks := th.App.Config().ServiceSettings.EnableIncomingWebhooks - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = enableIncomingHooks }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) hook := &model.IncomingWebhook{ChannelId: channel1.Id} @@ -962,20 +938,8 @@ func TestIncomingWebhooks(t *testing.T) { t.Fatal("should not have failed -- ExperimentalTownSquareIsReadOnly is false and it's not a read only channel") } - isLicensed := utils.IsLicensed() - license := utils.License() - disableTownSquareReadOnly := th.App.Config().TeamSettings.ExperimentalTownSquareIsReadOnly - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = disableTownSquareReadOnly }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.ExperimentalTownSquareIsReadOnly = true }) - th.App.SetDefaultRolesBasedOnConfig() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) if _, err := th.BasicClient.DoPost(url, fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", model.DEFAULT_CHANNEL), "application/json"); err == nil { t.Fatal("should have failed -- ExperimentalTownSquareIsReadOnly is true and it's a read only channel") diff --git a/api4/channel_test.go b/api4/channel_test.go index 724b0d84b0..e659187070 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -14,7 +14,6 @@ import ( "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/utils" ) func TestCreateChannel(t *testing.T) { @@ -82,23 +81,9 @@ func TestCreateChannel(t *testing.T) { th.LoginBasic() // Check permissions with policy config changes - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelCreation - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelCreation - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -110,11 +95,8 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.CreateChannel(channel) CheckForbiddenStatus(t, resp) @@ -142,11 +124,8 @@ func TestCreateChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelCreation = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() @@ -173,9 +152,7 @@ func TestCreateChannel(t *testing.T) { CheckNoError(t, resp) // Check that if unlicensed the policy restriction is not enforced. - utils.SetIsLicensed(false) - utils.SetLicense(nil) - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(nil) channel.Name = GenerateTestChannelName() _, resp = Client.CreateChannel(channel) @@ -887,23 +864,9 @@ func TestDeleteChannel(t *testing.T) { th.InitBasic().InitSystemAdmin() - isLicensed := utils.IsLicensed() - license := utils.License() - restrictPublicChannel := *th.App.Config().TeamSettings.RestrictPublicChannelManagement - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManagement - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = restrictPublicChannel }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = restrictPrivateChannel }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelManagement = model.PERMISSIONS_ALL }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManagement = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client = th.Client team = th.BasicTeam @@ -926,11 +889,8 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -967,9 +927,7 @@ func TestDeleteChannel(t *testing.T) { // successful delete by team admin th.UpdateUserToTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckNoError(t, resp) @@ -979,16 +937,11 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_TEAM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.UpdateUserToNonTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -1018,9 +971,7 @@ func TestDeleteChannel(t *testing.T) { // successful delete by team admin th.UpdateUserToTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckNoError(t, resp) @@ -1030,11 +981,8 @@ func TestDeleteChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPublicChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN - }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelDeletion = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // channels created by SystemAdmin publicChannel6 = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_OPEN) @@ -1064,9 +1012,7 @@ func TestDeleteChannel(t *testing.T) { // cannot delete by team admin th.UpdateUserToTeamAdmin(th.BasicUser, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.DeleteChannel(publicChannel6.Id) CheckForbiddenStatus(t, resp) @@ -1823,16 +1769,9 @@ func TestAddChannelMember(t *testing.T) { CheckNoError(t, resp) // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1846,18 +1785,8 @@ func TestAddChannelMember(t *testing.T) { Client.Logout() // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can add other users. Client.Login(user2.Username, user2.Password) @@ -1875,10 +1804,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1893,10 +1818,7 @@ func TestAddChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client.Login(user.Username, user.Password) _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) @@ -1907,10 +1829,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -1925,10 +1843,7 @@ func TestAddChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) Client.Login(user.Username, user.Password) _, resp = Client.AddChannelMember(privateChannel.Id, user3.Id) @@ -1939,10 +1854,6 @@ func TestAddChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() Client.Login(user2.Username, user2.Password) privateChannel = th.CreatePrivateChannel() @@ -2019,16 +1930,9 @@ func TestRemoveChannelMember(t *testing.T) { th.App.InvalidateAllCaches() // Test policy does not apply to TE. - restrictPrivateChannel := *th.App.Config().TeamSettings.RestrictPrivateChannelManageMembers - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.TeamSettings.RestrictPrivateChannelManageMembers = restrictPrivateChannel - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() privateChannel := th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2040,18 +1944,8 @@ func TestRemoveChannelMember(t *testing.T) { CheckNoError(t, resp) // Add a license - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) // Check that a regular channel user can remove other users. privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) @@ -2067,10 +1961,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_CHANNEL_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2083,9 +1973,7 @@ func TestRemoveChannelMember(t *testing.T) { th.MakeUserChannelAdmin(user1, privateChannel) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) @@ -2094,10 +1982,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) @@ -2110,9 +1994,7 @@ func TestRemoveChannelMember(t *testing.T) { th.UpdateUserToTeamAdmin(user1, team) th.App.InvalidateAllCaches() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) _, resp = Client.RemoveUserFromChannel(privateChannel.Id, user2.Id) CheckNoError(t, resp) @@ -2121,10 +2003,6 @@ func TestRemoveChannelMember(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictPrivateChannelManageMembers = model.PERMISSIONS_SYSTEM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() privateChannel = th.CreateChannelWithClient(th.SystemAdminClient, model.CHANNEL_PRIVATE) _, resp = th.SystemAdminClient.AddChannelMember(privateChannel.Id, user1.Id) diff --git a/api4/post_test.go b/api4/post_test.go index 6f770b70a3..2579185259 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -17,7 +17,6 @@ import ( "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func TestCreatePost(t *testing.T) { @@ -130,20 +129,8 @@ func testCreatePostWithOutgoingHook( team := th.BasicTeam channel := th.BasicChannel - enableOutgoingHooks := th.App.Config().ServiceSettings.EnableOutgoingWebhooks - enableAdminOnlyHooks := th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = enableOutgoingHooks }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOnlyAdminIntegrations = enableAdminOnlyHooks }) - th.App.SetDefaultRolesBasedOnConfig() - th.App.UpdateConfig(func(cfg *model.Config) { - cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections - }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost 127.0.0.1" }) @@ -489,21 +476,8 @@ func TestUpdatePost(t *testing.T) { Client := th.Client channel := th.BasicChannel - isLicensed := utils.IsLicensed() - license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a"} rpost, resp := Client.CreatePost(post) @@ -574,21 +548,8 @@ func TestPatchPost(t *testing.T) { Client := th.Client channel := th.BasicChannel - isLicensed := utils.IsLicensed() - license := utils.License() - allowEditPost := *th.App.Config().ServiceSettings.AllowEditPost - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = allowEditPost }) - th.App.SetDefaultRolesBasedOnConfig() - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowEditPost = model.ALLOW_EDIT_POST_ALWAYS }) - th.App.SetDefaultRolesBasedOnConfig() post := &model.Post{ ChannelId: channel.Id, diff --git a/api4/team_test.go b/api4/team_test.go index a8696a30b5..56e6d575de 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -68,13 +68,7 @@ func TestCreateTeam(t *testing.T) { CheckUnauthorizedStatus(t, resp) // Update permission - enableTeamCreation := th.App.Config().TeamSettings.EnableTeamCreation - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = enableTeamCreation }) - th.App.SetDefaultRolesBasedOnConfig() - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.EnableTeamCreation = false }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() _, resp = Client.CreateTeam(team) @@ -1292,20 +1286,8 @@ func TestAddTeamMember(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Test without the EE license to see that the permission restriction is ignored. @@ -1313,10 +1295,7 @@ func TestAddTeamMember(t *testing.T) { CheckNoError(t, resp) // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1327,10 +1306,7 @@ func TestAddTeamMember(t *testing.T) { th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a team admin. @@ -1339,7 +1315,6 @@ func TestAddTeamMember(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) @@ -1353,21 +1328,13 @@ func TestAddTeamMember(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a regular user. _, resp = Client.AddTeamMember(team.Id, otherUser.Id) CheckNoError(t, resp) - // Reset config and license. - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // by hash and data @@ -1506,20 +1473,8 @@ func TestAddTeamMembers(t *testing.T) { Client.Logout() - // Check effects of config and license changes. - restrictTeamInvite := *th.App.Config().TeamSettings.RestrictTeamInvite - isLicensed := utils.IsLicensed() - license := utils.License() - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = restrictTeamInvite }) - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.SetDefaultRolesBasedOnConfig() - }() - // Set the config so that only team admins can add a user to a team. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() th.LoginBasic() // Test without the EE license to see that the permission restriction is ignored. @@ -1527,10 +1482,7 @@ func TestAddTeamMembers(t *testing.T) { CheckNoError(t, resp) // Add an EE license. - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Check that a regular user can't add someone to the team. @@ -1541,10 +1493,7 @@ func TestAddTeamMembers(t *testing.T) { th.UpdateUserToTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_TEAM_ADMIN }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a team admin. @@ -1553,7 +1502,6 @@ func TestAddTeamMembers(t *testing.T) { // Change permission level to System Admin th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_SYSTEM_ADMIN }) - th.App.SetDefaultRolesBasedOnConfig() // Should not work as team admin. _, resp = Client.AddTeamMembers(team.Id, userList) @@ -1567,10 +1515,7 @@ func TestAddTeamMembers(t *testing.T) { th.UpdateUserToNonTeamAdmin(th.BasicUser, th.BasicTeam) th.App.InvalidateAllCaches() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.RestrictTeamInvite = model.PERMISSIONS_ALL }) - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - th.App.SetDefaultRolesBasedOnConfig() + th.App.SetLicense(model.NewTestLicense()) th.LoginBasic() // Should work as a regular user. @@ -1929,10 +1874,6 @@ func TestInviteUsersToTeam(t *testing.T) { } } - restrictCreationToDomains := th.App.Config().TeamSettings.RestrictCreationToDomains - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = restrictCreationToDomains }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = "@example.com" }) err := th.App.InviteNewUsersToTeam(emailList, th.BasicTeam.Id, th.BasicUser.Id) diff --git a/api4/user_test.go b/api4/user_test.go index a7b7d297d4..4613a8ea9c 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -1566,18 +1566,7 @@ func TestUpdateUserMfa(t *testing.T) { defer th.TearDown() Client := th.Client - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) session, _ := th.App.GetSession(Client.AuthToken) @@ -1612,18 +1601,7 @@ func TestCheckUserMfa(t *testing.T) { t.Fatal("should be false - mfa not active") } - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) th.LoginBasic() @@ -1659,18 +1637,7 @@ func TestGenerateMfaSecret(t *testing.T) { _, resp = Client.GenerateMfaSecret("junk") CheckBadRequestStatus(t, resp) - isLicensed := utils.IsLicensed() - license := utils.License() - enableMfa := *th.App.Config().ServiceSettings.EnableMultifactorAuthentication - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = enableMfa }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.MFA = true + th.App.SetLicense(model.NewTestLicense("mfa")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableMultifactorAuthentication = true }) _, resp = Client.GenerateMfaSecret(model.NewId()) @@ -2187,19 +2154,7 @@ func TestSwitchAccount(t *testing.T) { t.Fatal("bad link") } - isLicensed := utils.IsLicensed() - license := utils.License() - enableAuthenticationTransfer := *th.App.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { - *cfg.ServiceSettings.ExperimentalEnableAuthenticationTransfer = enableAuthenticationTransfer - }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ExperimentalEnableAuthenticationTransfer = false }) sr = &model.SwitchRequest{ diff --git a/app/app.go b/app/app.go index 0b5efa76b3..3c37ec252c 100644 --- a/app/app.go +++ b/app/app.go @@ -88,6 +88,9 @@ func New(options ...Option) (*App, error) { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") } + // TODO: remove this once utils global license state is eliminated + utils.SetLicense(nil) + app := &App{ goroutineExitSignal: make(chan struct{}, 1), Srv: &Server{ diff --git a/app/config.go b/app/config.go index 46426c442a..55be24352f 100644 --- a/app/config.go +++ b/app/config.go @@ -254,11 +254,3 @@ func (a *App) Desanitize(cfg *model.Config) { cfg.SqlSettings.DataSourceSearchReplicas[i] = actual.SqlSettings.DataSourceSearchReplicas[i] } } - -// License returns the currently active license or nil if the application is unlicensed. -func (a *App) License() *model.License { - if utils.IsLicensed() { - return utils.License() - } - return nil -} diff --git a/app/license.go b/app/license.go index c7fd07197d..7402b5c223 100644 --- a/app/license.go +++ b/app/license.go @@ -59,7 +59,7 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) } } - if ok := utils.SetLicense(license); !ok { + if ok := a.SetLicense(license); !ok { return nil, model.NewAppError("addLicense", model.EXPIRED_LICENSE_ERROR, nil, "", http.StatusBadRequest) } @@ -102,6 +102,20 @@ func (a *App) SaveLicense(licenseBytes []byte) (*model.License, *model.AppError) return license, nil } +// License returns the currently active license or nil if the application is unlicensed. +func (a *App) License() *model.License { + if utils.IsLicensed() { + return utils.License() + } + return nil +} + +func (a *App) SetLicense(license *model.License) bool { + ok := utils.SetLicense(license) + a.SetDefaultRolesBasedOnConfig() + return ok +} + func (a *App) RemoveLicense() *model.AppError { utils.RemoveLicense() diff --git a/app/session_test.go b/app/session_test.go index bca3b59b7e..09d81f4ac3 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -48,18 +48,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { session, _ = th.App.CreateSession(session) - isLicensed := utils.IsLicensed() - license := utils.License() - timeout := *th.App.Config().ServiceSettings.SessionIdleTimeoutInMinutes - defer func() { - utils.SetIsLicensed(isLicensed) - utils.SetLicense(license) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = timeout }) - }() - utils.SetIsLicensed(true) - utils.SetLicense(&model.License{Features: &model.Features{}}) - utils.License().Features.SetDefaults() - *utils.License().Features.Compliance = true + th.App.SetLicense(model.NewTestLicense("compliance")) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 5 }) rsession, err := th.App.GetSession(session.Token) diff --git a/model/license.go b/model/license.go index f96cba06c5..942a18d555 100644 --- a/model/license.go +++ b/model/license.go @@ -173,6 +173,25 @@ func (l *License) ToJson() string { return string(b) } +// NewTestLicense returns a license that expires in the future and has the given features. +func NewTestLicense(features ...string) *License { + ret := &License{ + ExpiresAt: GetMillis() + 90*24*60*60*1000, + Customer: &Customer{}, + Features: &Features{}, + } + ret.Features.SetDefaults() + + featureMap := map[string]bool{} + for _, feature := range features { + featureMap[feature] = true + } + featureJson, _ := json.Marshal(featureMap) + json.Unmarshal(featureJson, &ret.Features) + + return ret +} + func LicenseFromJson(data io.Reader) *License { var o *License json.NewDecoder(data).Decode(&o) From cf929476bdaa9a388fdfde62889bbef2a4dbf1c2 Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Feb 2018 10:54:45 -0600 Subject: [PATCH 08/14] fix client4 post sanitization (#8219) --- model/client4.go | 4 +-- model/client4_test.go | 58 +++++++++++++++++++++++++++++++++++++++++++ model/post.go | 13 +++++----- 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 model/client4_test.go diff --git a/model/client4.go b/model/client4.go index 0694ecbdf0..962b816bb2 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1729,7 +1729,7 @@ func (c *Client4) RemoveUserFromChannel(channelId, userId string) (bool, *Respon // CreatePost creates a post based on the provided post struct. func (c *Client4) CreatePost(post *Post) (*Post, *Response) { - if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToJson()); err != nil { + if r, err := c.DoApiPost(c.GetPostsRoute(), post.ToUnsanitizedJson()); err != nil { return nil, BuildErrorResponse(r, err) } else { defer closeBody(r) @@ -1739,7 +1739,7 @@ func (c *Client4) CreatePost(post *Post) (*Post, *Response) { // UpdatePost updates a post based on the provided post struct. func (c *Client4) UpdatePost(postId string, post *Post) (*Post, *Response) { - if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToJson()); err != nil { + if r, err := c.DoApiPut(c.GetPostRoute(postId), post.ToUnsanitizedJson()); err != nil { return nil, BuildErrorResponse(r, err) } else { defer closeBody(r) diff --git a/model/client4_test.go b/model/client4_test.go new file mode 100644 index 0000000000..f7923fa8fa --- /dev/null +++ b/model/client4_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package model + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// https://github.com/mattermost/mattermost-server/issues/8205 +func TestClient4CreatePost(t *testing.T) { + post := &Post{ + Props: map[string]interface{}{ + "attachments": []*SlackAttachment{ + &SlackAttachment{ + Actions: []*PostAction{ + &PostAction{ + Integration: &PostActionIntegration{ + Context: map[string]interface{}{ + "foo": "bar", + }, + URL: "http://foo.com", + }, + Name: "Foo", + }, + }, + }, + }, + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + attachments := PostFromJson(r.Body).Attachments() + assert.Equal(t, []*SlackAttachment{ + &SlackAttachment{ + Actions: []*PostAction{ + &PostAction{ + Integration: &PostActionIntegration{ + Context: map[string]interface{}{ + "foo": "bar", + }, + URL: "http://foo.com", + }, + Name: "Foo", + }, + }, + }, + }, attachments) + })) + + client := NewAPIv4Client(server.URL) + _, resp := client.CreatePost(post) + assert.Equal(t, http.StatusOK, resp.StatusCode) +} diff --git a/model/post.go b/model/post.go index 7cf0f1b35b..4a774b5d4e 100644 --- a/model/post.go +++ b/model/post.go @@ -122,12 +122,13 @@ type PostActionIntegrationResponse struct { func (o *Post) ToJson() string { copy := *o copy.StripActionIntegrations() - b, err := json.Marshal(©) - if err != nil { - return "" - } else { - return string(b) - } + b, _ := json.Marshal(©) + return string(b) +} + +func (o *Post) ToUnsanitizedJson() string { + b, _ := json.Marshal(o) + return string(b) } func PostFromJson(data io.Reader) *Post { From 1b064c674a21fbee4000fdf84fbdc75da1fab1f0 Mon Sep 17 00:00:00 2001 From: Derrick Anderson Date: Thu, 8 Feb 2018 12:12:48 -0500 Subject: [PATCH 09/14] Add prepatory code for 4.8.0 (#8226) * Add prepatory code for 4.8.0 * formatting issue * build bug --- store/sqlstore/upgrade.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 0de91f28bc..bebcc262cd 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -15,6 +15,7 @@ import ( ) const ( + VERSION_4_8_0 = "4.8.0" VERSION_4_7_0 = "4.7.0" VERSION_4_6_0 = "4.6.0" VERSION_4_5_0 = "4.5.0" @@ -64,6 +65,7 @@ func UpgradeDatabase(sqlStore SqlStore) { UpgradeDatabaseToVersion45(sqlStore) UpgradeDatabaseToVersion46(sqlStore) UpgradeDatabaseToVersion47(sqlStore) + UpgradeDatabaseToVersion48(sqlStore) // If the SchemaVersion is empty this this is the first time it has ran // so lets set it to the current version. @@ -346,3 +348,10 @@ func UpgradeDatabaseToVersion47(sqlStore SqlStore) { saveSchemaVersion(sqlStore, VERSION_4_7_0) } } + +func UpgradeDatabaseToVersion48(sqlStore SqlStore) { + //TODO: Uncomment the following condition when version 4.8.0 is released + //if shouldPerformUpgrade(sqlStore, VERSION_4_7_0, VERSION_4_8_0) { + // saveSchemaVersion(sqlStore, VERSION_4_8_0) + //} +} From 7e8106b95c11a4187f8c00256f3067433d20b24e Mon Sep 17 00:00:00 2001 From: Chris Date: Thu, 8 Feb 2018 18:43:27 -0600 Subject: [PATCH 10/14] use mattermost fork of go-bindata (#8216) --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ca8fafdb3e..366c27057a 100644 --- a/Makefile +++ b/Makefile @@ -276,7 +276,7 @@ store-mocks: ## Creates mock files. GOPATH=$(shell go env GOPATH) $(shell go env GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.' update-jira-plugin: ## Updates Jira plugin. - go get github.com/jteeuwen/go-bindata/... + go get github.com/mattermost/go-bindata/... curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz $(shell go env GOPATH)/bin/go-bindata -pkg jira -o app/plugin/jira/plugin_darwin_amd64.go plugin.tar.gz curl -s https://api.github.com/repos/mattermost/mattermost-plugin-jira/releases/latest | grep browser_download_url | grep linux-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz @@ -287,7 +287,7 @@ update-jira-plugin: ## Updates Jira plugin. gofmt -s -w ./app/plugin/jira update-zoom-plugin: ## Updates Zoom plugin. - go get github.com/jteeuwen/go-bindata/... + go get github.com/mattermost/go-bindata/... curl -s https://api.github.com/repos/mattermost/mattermost-plugin-zoom/releases/latest | grep browser_download_url | grep darwin-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz $(shell go env GOPATH)/bin/go-bindata -pkg zoom -o app/plugin/zoom/plugin_darwin_amd64.go plugin.tar.gz curl -s https://api.github.com/repos/mattermost/mattermost-plugin-zoom/releases/latest | grep browser_download_url | grep linux-amd64 | cut -d '"' -f 4 | wget -qi - -O plugin.tar.gz From a6309aaf48e216fe5f6779188071d4b621b643b6 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 10:04:48 -0600 Subject: [PATCH 11/14] Remove license globals entirely (#8229) * remove license globals entirely * fix infinite recursion * test fix --- api/apitestlib.go | 5 +- api/license.go | 7 +- api/license_test.go | 8 +-- api/user.go | 4 +- api4/apitestlib.go | 5 +- api4/oauth_test.go | 62 ----------------- api4/system.go | 6 +- app/admin.go | 3 +- app/app.go | 24 ++++--- app/apptestlib.go | 5 +- app/config.go | 2 +- app/email.go | 3 +- app/file.go | 3 +- app/license.go | 105 ++++++++++++++++++++++++++--- app/license_test.go | 75 ++++++++++++++++++++- app/role.go | 6 +- app/session_test.go | 9 ++- cmd/platform/jobserver.go | 2 +- jobs/server.go | 33 --------- jobs/server_test.go | 39 ----------- utils/authorization.go | 20 +++--- utils/config.go | 31 +++++---- utils/config_test.go | 2 +- utils/file_backend.go | 4 +- utils/file_backend_test.go | 2 +- utils/license.go | 135 +------------------------------------ utils/license_test.go | 67 ------------------ utils/mail.go | 12 ++-- utils/mail_test.go | 6 +- 29 files changed, 256 insertions(+), 429 deletions(-) delete mode 100644 jobs/server_test.go diff --git a/api/apitestlib.go b/api/apitestlib.go index bae00927a0..47691b2dd0 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -118,9 +118,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } return th diff --git a/api/license.go b/api/license.go index 8eb7803e11..432442ad65 100644 --- a/api/license.go +++ b/api/license.go @@ -9,7 +9,6 @@ import ( "net/http" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) func (api *API) InitLicense() { @@ -83,7 +82,7 @@ func removeLicense(c *Context, w http.ResponseWriter, r *http.Request) { func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) { useSanitizedLicense := !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) - etag := utils.GetClientLicenseEtag(useSanitizedLicense) + etag := c.App.GetClientLicenseEtag(useSanitizedLicense) if c.HandleEtag(etag, "Get Client License Config", w, r) { return } @@ -91,9 +90,9 @@ func getClientLicenceConfig(c *Context, w http.ResponseWriter, r *http.Request) var clientLicense map[string]string if useSanitizedLicense { - clientLicense = utils.ClientLicense() + clientLicense = c.App.ClientLicense() } else { - clientLicense = utils.GetSanitizedClientLicense() + clientLicense = c.App.GetSanitizedClientLicense() } w.Header().Set(model.HEADER_ETAG_SERVER, etag) diff --git a/api/license_test.go b/api/license_test.go index 50d73101d8..47586151ac 100644 --- a/api/license_test.go +++ b/api/license_test.go @@ -5,8 +5,6 @@ package api import ( "testing" - - "github.com/mattermost/mattermost-server/utils" ) func TestGetLicenceConfig(t *testing.T) { @@ -32,7 +30,7 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("cache should be empty") } - utils.SetClientLicense(map[string]string{"IsLicensed": "true"}) + th.App.SetClientLicense(map[string]string{"IsLicensed": "true"}) if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { t.Fatal(err) @@ -40,7 +38,7 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("result should not be empty") } - utils.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) if cache_result, err := Client.GetClientLicenceConfig(result.Etag); err != nil { t.Fatal(err) @@ -48,6 +46,6 @@ func TestGetLicenceConfig(t *testing.T) { t.Fatal("result should not be empty") } - utils.SetClientLicense(map[string]string{"IsLicensed": "false"}) + th.App.SetClientLicense(map[string]string{"IsLicensed": "false"}) } } diff --git a/api/user.go b/api/user.go index 440ea58580..ad4f12ef3c 100644 --- a/api/user.go +++ b/api/user.go @@ -299,9 +299,9 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) { il.ClientCfg = c.App.ClientConfig() if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - il.LicenseCfg = utils.ClientLicense() + il.LicenseCfg = c.App.ClientLicense() } else { - il.LicenseCfg = utils.GetSanitizedClientLicense() + il.LicenseCfg = c.App.GetSanitizedClientLicense() } w.Write([]byte(il.ToJson())) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index b55ce7cbfc..db43a65123 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -125,9 +125,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } th.Client = th.CreateClient() diff --git a/api4/oauth_test.go b/api4/oauth_test.go index 8dd602456c..c871dafff7 100644 --- a/api4/oauth_test.go +++ b/api4/oauth_test.go @@ -18,14 +18,7 @@ func TestCreateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true} @@ -42,12 +35,10 @@ func TestCreateOAuthApp(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.CreateOAuthApp(oapp) CheckForbiddenStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() rapp, resp = Client.CreateOAuthApp(oapp) CheckNoError(t, resp) CheckCreatedStatus(t, resp) @@ -86,14 +77,7 @@ func TestUpdateOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{ Name: "oapp", @@ -172,7 +156,6 @@ func TestUpdateOAuthApp(t *testing.T) { th.LoginBasic() th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.UpdateOAuthApp(oapp) CheckForbiddenStatus(t, resp) @@ -199,15 +182,8 @@ func TestGetOAuthApps(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -251,7 +227,6 @@ func TestGetOAuthApps(t *testing.T) { } th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthApps(0, 1000) CheckForbiddenStatus(t, resp) @@ -272,15 +247,8 @@ func TestGetOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -320,7 +288,6 @@ func TestGetOAuthApp(t *testing.T) { CheckForbiddenStatus(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthApp(rapp2.Id) CheckForbiddenStatus(t, resp) @@ -347,15 +314,8 @@ func TestGetOAuthAppInfo(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -395,7 +355,6 @@ func TestGetOAuthAppInfo(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = true }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.GetOAuthAppInfo(rapp2.Id) CheckNoError(t, resp) @@ -422,15 +381,8 @@ func TestDeleteOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -465,7 +417,6 @@ func TestDeleteOAuthApp(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.DeleteOAuthApp(rapp.Id) CheckForbiddenStatus(t, resp) @@ -490,15 +441,8 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - adminOnly := *th.App.Config().ServiceSettings.EnableOnlyAdminIntegrations - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = adminOnly }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} @@ -537,7 +481,6 @@ func TestRegenerateOAuthAppSecret(t *testing.T) { CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOnlyAdminIntegrations = false }) - th.App.SetDefaultRolesBasedOnConfig() _, resp = Client.RegenerateOAuthAppSecret(rapp.Id) CheckForbiddenStatus(t, resp) @@ -622,12 +565,7 @@ func TestAuthorizeOAuthApp(t *testing.T) { Client := th.Client AdminClient := th.SystemAdminClient - enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider - defer func() { - th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth }) - }() th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true }) - th.App.SetDefaultRolesBasedOnConfig() oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}} diff --git a/api4/system.go b/api4/system.go index 43b941247a..061ffe0940 100644 --- a/api4/system.go +++ b/api4/system.go @@ -266,7 +266,7 @@ func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { return } - etag := utils.GetClientLicenseEtag(true) + etag := c.App.GetClientLicenseEtag(true) if c.HandleEtag(etag, "Get Client License", w, r) { return } @@ -274,9 +274,9 @@ func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) { var clientLicense map[string]string if c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - clientLicense = utils.ClientLicense() + clientLicense = c.App.ClientLicense() } else { - clientLicense = utils.GetSanitizedClientLicense() + clientLicense = c.App.GetSanitizedClientLicense() } w.Header().Set(model.HEADER_ETAG_SERVER, etag) diff --git a/app/admin.go b/app/admin.go index b838ed3bde..154fa88991 100644 --- a/app/admin.go +++ b/app/admin.go @@ -237,7 +237,8 @@ func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { return err } else { T := utils.GetUserTranslations(user.Locale) - if err := utils.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg); err != nil { + license := a.License() + if err := utils.SendMailUsingConfig(user.Email, T("api.admin.test_email.subject"), T("api.admin.test_email.body"), cfg, license != nil && *license.Features.Compliance); err != nil { return err } } diff --git a/app/app.go b/app/app.go index 3c37ec252c..dd5deb342d 100644 --- a/app/app.go +++ b/app/app.go @@ -59,6 +59,10 @@ type App struct { configFile string configListeners map[string]func(*model.Config, *model.Config) + licenseValue atomic.Value + clientLicenseValue atomic.Value + licenseListeners map[string]func() + newStore func() store.Store htmlTemplateWatcher *utils.HTMLTemplateWatcher @@ -88,18 +92,16 @@ func New(options ...Option) (*App, error) { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") } - // TODO: remove this once utils global license state is eliminated - utils.SetLicense(nil) - app := &App{ goroutineExitSignal: make(chan struct{}, 1), Srv: &Server{ Router: mux.NewRouter(), }, - sessionCache: utils.NewLru(model.SESSION_CACHE_SIZE), - configFile: "config.json", - configListeners: make(map[string]func(*model.Config, *model.Config)), - 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(){}, } for _, option := range options { @@ -123,9 +125,9 @@ func New(options ...Option) (*App, error) { app.configListenerId = app.AddConfigListener(func(_, _ *model.Config) { app.configOrLicenseListener() }) - app.licenseListenerId = utils.AddLicenseListener(app.configOrLicenseListener) + app.licenseListenerId = app.AddLicenseListener(app.configOrLicenseListener) app.regenerateClientConfig() - app.SetDefaultRolesBasedOnConfig() + app.setDefaultRolesBasedOnConfig() l4g.Info(utils.T("api.server.new_server.init.info")) @@ -166,7 +168,7 @@ func New(options ...Option) (*App, error) { func (a *App) configOrLicenseListener() { a.regenerateClientConfig() - a.SetDefaultRolesBasedOnConfig() + a.setDefaultRolesBasedOnConfig() } func (a *App) Shutdown() { @@ -188,7 +190,7 @@ func (a *App) Shutdown() { } a.RemoveConfigListener(a.configListenerId) - utils.RemoveLicenseListener(a.licenseListenerId) + a.RemoveLicenseListener(a.licenseListenerId) l4g.Info(utils.T("api.server.stop_server.stopped.info")) a.DisableConfigWatch() diff --git a/app/apptestlib.go b/app/apptestlib.go index 016a68becf..c7846c9b56 100644 --- a/app/apptestlib.go +++ b/app/apptestlib.go @@ -106,9 +106,10 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.EnableOpenServer = true }) - utils.SetIsLicensed(enterprise) if enterprise { - utils.License().Features.SetDefaults() + th.App.SetLicense(model.NewTestLicense()) + } else { + th.App.SetLicense(nil) } return th diff --git a/app/config.go b/app/config.go index 55be24352f..b4925e8fb7 100644 --- a/app/config.go +++ b/app/config.go @@ -201,7 +201,7 @@ func (a *App) AsymmetricSigningKey() *ecdsa.PrivateKey { } func (a *App) regenerateClientConfig() { - a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId()) + a.clientConfig = utils.GenerateClientConfig(a.Config(), a.DiagnosticId(), a.License()) if key := a.AsymmetricSigningKey(); key != nil { der, _ := x509.MarshalPKIXPublicKey(&key.PublicKey) a.clientConfig["AsymmetricSigningPublicKey"] = base64.StdEncoding.EncodeToString(der) diff --git a/app/email.go b/app/email.go index b809b972d1..89de2ae657 100644 --- a/app/email.go +++ b/app/email.go @@ -316,5 +316,6 @@ func (a *App) NewEmailTemplate(name, locale string) *utils.HTMLTemplate { } func (a *App) SendMail(to, subject, htmlBody string) *model.AppError { - return utils.SendMailUsingConfig(to, subject, htmlBody, a.Config()) + license := a.License() + return utils.SendMailUsingConfig(to, subject, htmlBody, a.Config(), license != nil && *license.Features.Compliance) } diff --git a/app/file.go b/app/file.go index d66c64adbf..bb20585bb2 100644 --- a/app/file.go +++ b/app/file.go @@ -58,7 +58,8 @@ const ( ) func (a *App) FileBackend() (utils.FileBackend, *model.AppError) { - return utils.NewFileBackend(&a.Config().FileSettings) + license := a.License() + return utils.NewFileBackend(&a.Config().FileSettings, license != nil && *license.Features.Compliance) } func (a *App) ReadFile(path string) ([]byte, *model.AppError) { diff --git a/app/license.go b/app/license.go index 7402b5c223..efb725a208 100644 --- a/app/license.go +++ b/app/license.go @@ -4,16 +4,19 @@ package app import ( + "crypto/md5" + "fmt" "net/http" "strings" l4g "github.com/alecthomas/log4go" + "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" ) func (a *App) LoadLicense() { - utils.RemoveLicense() + a.SetLicense(nil) licenseId := "" if result := <-a.Srv.Store.System().Get(); result.Err == nil { @@ -36,7 +39,7 @@ func (a *App) LoadLicense() { if result := <-a.Srv.Store.License().Get(licenseId); result.Err == nil { record := result.Data.(*model.LicenseRecord) - utils.LoadLicense([]byte(record.Bytes)) + a.ValidateAndSetLicenseBytes([]byte(record.Bytes)) l4g.Info("License key valid unlocking enterprise features.") } else { l4g.Info(utils.T("mattermost.load_license.find.warn")) @@ -104,33 +107,113 @@ 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 { - if utils.IsLicensed() { - return utils.License() - } - return nil + license, _ := a.licenseValue.Load().(*model.License) + return license } func (a *App) SetLicense(license *model.License) bool { - ok := utils.SetLicense(license) - a.SetDefaultRolesBasedOnConfig() - return ok + defer func() { + a.setDefaultRolesBasedOnConfig() + for _, listener := range a.licenseListeners { + listener() + } + }() + + if license != nil { + license.Features.SetDefaults() + + if !license.IsExpired() { + a.licenseValue.Store(license) + a.clientLicenseValue.Store(utils.GetClientLicense(license)) + return true + } + } + + a.licenseValue.Store((*model.License)(nil)) + a.SetClientLicense(map[string]string{"IsLicensed": "false"}) + return false +} + +func (a *App) ValidateAndSetLicenseBytes(b []byte) { + if success, licenseStr := utils.ValidateLicense(b); success { + license := model.LicenseFromJson(strings.NewReader(licenseStr)) + a.SetLicense(license) + return + } + + l4g.Warn(utils.T("utils.license.load_license.invalid.warn")) +} + +func (a *App) SetClientLicense(m map[string]string) { + a.clientLicenseValue.Store(m) +} + +func (a *App) ClientLicense() map[string]string { + clientLicense, _ := a.clientLicenseValue.Load().(map[string]string) + return clientLicense } func (a *App) RemoveLicense() *model.AppError { - utils.RemoveLicense() + if license, _ := a.licenseValue.Load().(*model.License); license == nil { + return nil + } sysVar := &model.System{} sysVar.Name = model.SYSTEM_ACTIVE_LICENSE_ID sysVar.Value = "" if result := <-a.Srv.Store.System().SaveOrUpdate(sysVar); result.Err != nil { - utils.RemoveLicense() return result.Err } + a.SetLicense(nil) a.ReloadConfig() a.InvalidateAllCaches() return nil } + +func (a *App) AddLicenseListener(listener func()) string { + id := model.NewId() + a.licenseListeners[id] = listener + return id +} + +func (a *App) RemoveLicenseListener(id string) { + delete(a.licenseListeners, id) +} + +func (a *App) GetClientLicenseEtag(useSanitized bool) string { + value := "" + + lic := a.ClientLicense() + + if useSanitized { + lic = a.GetSanitizedClientLicense() + } + + for k, v := range lic { + value += fmt.Sprintf("%s:%s;", k, v) + } + + return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value)))) +} + +func (a *App) GetSanitizedClientLicense() map[string]string { + sanitizedLicense := make(map[string]string) + + for k, v := range a.ClientLicense() { + sanitizedLicense[k] = v + } + + delete(sanitizedLicense, "Id") + delete(sanitizedLicense, "Name") + delete(sanitizedLicense, "Email") + delete(sanitizedLicense, "PhoneNumber") + delete(sanitizedLicense, "IssuedAt") + delete(sanitizedLicense, "StartsAt") + delete(sanitizedLicense, "ExpiresAt") + + return sanitizedLicense +} diff --git a/app/license_test.go b/app/license_test.go index 5b73d9d186..f86d604d10 100644 --- a/app/license_test.go +++ b/app/license_test.go @@ -4,8 +4,9 @@ package app import ( - //"github.com/mattermost/mattermost-server/model" "testing" + + "github.com/mattermost/mattermost-server/model" ) func TestLoadLicense(t *testing.T) { @@ -37,3 +38,75 @@ func TestRemoveLicense(t *testing.T) { t.Fatal("should have removed license") } } + +func TestSetLicense(t *testing.T) { + th := Setup() + defer th.TearDown() + + l1 := &model.License{} + l1.Features = &model.Features{} + l1.Customer = &model.Customer{} + l1.StartsAt = model.GetMillis() - 1000 + l1.ExpiresAt = model.GetMillis() + 100000 + if ok := th.App.SetLicense(l1); !ok { + t.Fatal("license should have worked") + } + + l2 := &model.License{} + l2.Features = &model.Features{} + l2.Customer = &model.Customer{} + l2.StartsAt = model.GetMillis() - 1000 + l2.ExpiresAt = model.GetMillis() - 100 + if ok := th.App.SetLicense(l2); ok { + t.Fatal("license should have failed") + } + + l3 := &model.License{} + l3.Features = &model.Features{} + l3.Customer = &model.Customer{} + l3.StartsAt = model.GetMillis() + 10000 + l3.ExpiresAt = model.GetMillis() + 100000 + if ok := th.App.SetLicense(l3); !ok { + t.Fatal("license should have passed") + } +} + +func TestClientLicenseEtag(t *testing.T) { + th := Setup() + defer th.TearDown() + + etag1 := th.App.GetClientLicenseEtag(false) + + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) + + etag2 := th.App.GetClientLicenseEtag(false) + if etag1 == etag2 { + t.Fatal("etags should not match") + } + + th.App.SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"}) + + etag3 := th.App.GetClientLicenseEtag(false) + if etag2 == etag3 { + t.Fatal("etags should not match") + } +} + +func TestGetSanitizedClientLicense(t *testing.T) { + th := Setup() + defer th.TearDown() + + l1 := &model.License{} + l1.Features = &model.Features{} + l1.Customer = &model.Customer{} + l1.Customer.Name = "TestName" + l1.StartsAt = model.GetMillis() - 1000 + l1.ExpiresAt = model.GetMillis() + 100000 + th.App.SetLicense(l1) + + m := th.App.GetSanitizedClientLicense() + + if _, ok := m["Name"]; ok { + t.Fatal("should have been sanatized") + } +} diff --git a/app/role.go b/app/role.go index 5f39dd6239..9f271ea7a2 100644 --- a/app/role.go +++ b/app/role.go @@ -12,8 +12,6 @@ func (a *App) Role(id string) *model.Role { return a.roles[id] } -// Updates the roles based on the app config and the global license check. You may need to invoke -// this when license changes are made. -func (a *App) SetDefaultRolesBasedOnConfig() { - a.roles = utils.DefaultRolesBasedOnConfig(a.Config()) +func (a *App) setDefaultRolesBasedOnConfig() { + a.roles = utils.DefaultRolesBasedOnConfig(a.Config(), a.License() != nil) } diff --git a/app/session_test.go b/app/session_test.go index 09d81f4ac3..bf8198a4ed 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -6,11 +6,10 @@ package app import ( "testing" - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" ) func TestCache(t *testing.T) { @@ -111,7 +110,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { assert.Nil(t, err) // Test regular session with license off, should not timeout - *utils.License().Features.Compliance = false + th.App.SetLicense(nil) session = &model.Session{ UserId: model.NewId(), @@ -125,7 +124,7 @@ func TestGetSessionIdleTimeoutInMinutes(t *testing.T) { _, err = th.App.GetSession(session.Token) assert.Nil(t, err) - *utils.License().Features.Compliance = true + th.App.SetLicense(model.NewTestLicense("compliance")) // Test regular session with timeout set to 0, should not timeout th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SessionIdleTimeoutInMinutes = 0 }) diff --git a/cmd/platform/jobserver.go b/cmd/platform/jobserver.go index e664136c0b..044ee6b6a6 100644 --- a/cmd/platform/jobserver.go +++ b/cmd/platform/jobserver.go @@ -35,7 +35,7 @@ func jobserverCmdF(cmd *cobra.Command, args []string) { defer l4g.Close() defer a.Shutdown() - a.Jobs.LoadLicense() + a.LoadLicense() // Run jobs l4g.Info("Starting Mattermost job server") diff --git a/jobs/server.go b/jobs/server.go index 4015d581eb..01cf821dcb 100644 --- a/jobs/server.go +++ b/jobs/server.go @@ -4,12 +4,9 @@ package jobs import ( - l4g "github.com/alecthomas/log4go" - ejobs "github.com/mattermost/mattermost-server/einterfaces/jobs" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/utils" ) type ConfigService interface { @@ -50,36 +47,6 @@ func (srv *JobServer) Config() *model.Config { return srv.ConfigService.Config() } -func (srv *JobServer) LoadLicense() { - licenseId := "" - if result := <-srv.Store.System().Get(); result.Err == nil { - props := result.Data.(model.StringMap) - licenseId = props[model.SYSTEM_ACTIVE_LICENSE_ID] - } - - var licenseBytes []byte - - if len(licenseId) != 26 { - // Lets attempt to load the file from disk since it was missing from the DB - _, licenseBytes = utils.GetAndValidateLicenseFileFromDisk(*srv.ConfigService.Config().ServiceSettings.LicenseFileLocation) - } else { - if result := <-srv.Store.License().Get(licenseId); result.Err == nil { - record := result.Data.(*model.LicenseRecord) - licenseBytes = []byte(record.Bytes) - l4g.Info("License key valid unlocking enterprise features.") - } else { - l4g.Info(utils.T("mattermost.load_license.find.warn")) - } - } - - if licenseBytes != nil { - utils.LoadLicense(licenseBytes) - l4g.Info("License key valid unlocking enterprise features.") - } else { - l4g.Info(utils.T("mattermost.load_license.find.warn")) - } -} - func (srv *JobServer) StartWorkers() { srv.Workers = srv.InitWorkers().Start() } diff --git a/jobs/server_test.go b/jobs/server_test.go deleted file mode 100644 index 3b5ef6f3df..0000000000 --- a/jobs/server_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package jobs - -import ( - "testing" - - "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" -) - -func TestJobServer_LoadLicense(t *testing.T) { - if utils.T == nil { - utils.TranslationsPreInit() - } - - mockStore := &storetest.Store{} - defer mockStore.AssertExpectations(t) - - server := &JobServer{ - Store: mockStore, - } - - mockStore.SystemStore.On("Get").Return(storetest.NewStoreChannel(store.StoreResult{ - Data: model.StringMap{ - model.SYSTEM_ACTIVE_LICENSE_ID: "thelicenseid00000000000000", - }, - })) - mockStore.LicenseStore.On("Get", "thelicenseid00000000000000").Return(storetest.NewStoreChannel(store.StoreResult{ - Data: &model.LicenseRecord{ - Id: "thelicenseid00000000000000", - }, - })) - - server.LoadLicense() -} diff --git a/utils/authorization.go b/utils/authorization.go index 39a0d606c6..42815b807f 100644 --- a/utils/authorization.go +++ b/utils/authorization.go @@ -7,7 +7,7 @@ import ( "github.com/mattermost/mattermost-server/model" ) -func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { +func DefaultRolesBasedOnConfig(cfg *model.Config, isLicensed bool) map[string]*model.Role { roles := make(map[string]*model.Role) for id, role := range model.DefaultRoles { copy := &model.Role{} @@ -15,7 +15,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { roles[id] = copy } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -35,7 +35,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelManagement { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -64,7 +64,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPublicChannelDeletion { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -93,7 +93,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelCreation { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -113,7 +113,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManagement { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -142,7 +142,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelDeletion { case model.PERMISSIONS_ALL: roles[model.TEAM_USER_ROLE_ID].Permissions = append( @@ -172,7 +172,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { } // Restrict permissions for Private Channel Manage Members - if IsLicensed() { + if isLicensed { switch *cfg.TeamSettings.RestrictPrivateChannelManageMembers { case model.PERMISSIONS_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( @@ -214,7 +214,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { } // Grant permissions for inviting and adding users to a team. - if IsLicensed() { + if isLicensed { if *cfg.TeamSettings.RestrictTeamInvite == model.PERMISSIONS_TEAM_ADMIN { roles[model.TEAM_ADMIN_ROLE_ID].Permissions = append( roles[model.TEAM_ADMIN_ROLE_ID].Permissions, @@ -236,7 +236,7 @@ func DefaultRolesBasedOnConfig(cfg *model.Config) map[string]*model.Role { ) } - if IsLicensed() { + if isLicensed { switch *cfg.ServiceSettings.RestrictPostDelete { case model.PERMISSIONS_DELETE_POST_ALL: roles[model.CHANNEL_USER_ROLE_ID].Permissions = append( diff --git a/utils/config.go b/utils/config.go index 9e962eef47..0669d63e41 100644 --- a/utils/config.go +++ b/utils/config.go @@ -342,7 +342,7 @@ func LoadConfig(fileName string) (config *model.Config, configPath string, appEr return config, configPath, nil } -func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]string { +func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.License) map[string]string { props := make(map[string]string) props["Version"] = model.CurrentVersion @@ -456,18 +456,17 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) - if IsLicensed() { - License := License() + if license != nil { props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) - if *License.Features.CustomBrand { + if *license.Features.CustomBrand { props["EnableCustomBrand"] = strconv.FormatBool(*c.TeamSettings.EnableCustomBrand) props["CustomBrandText"] = *c.TeamSettings.CustomBrandText props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText } - if *License.Features.LDAP { + if *license.Features.LDAP { props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable) props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "") @@ -478,16 +477,16 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor } - if *License.Features.MFA { + if *license.Features.MFA { props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication) props["EnforceMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnforceMultifactorAuthentication) } - if *License.Features.Compliance { + if *license.Features.Compliance { props["EnableCompliance"] = strconv.FormatBool(*c.ComplianceSettings.Enable) } - if *License.Features.SAML { + if *license.Features.SAML { props["EnableSaml"] = strconv.FormatBool(*c.SamlSettings.Enable) props["SamlLoginButtonText"] = *c.SamlSettings.LoginButtonText props["SamlFirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "") @@ -498,23 +497,23 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["SamlLoginButtonTextColor"] = *c.SamlSettings.LoginButtonTextColor } - if *License.Features.Cluster { + if *license.Features.Cluster { props["EnableCluster"] = strconv.FormatBool(*c.ClusterSettings.Enable) } - if *License.Features.Cluster { + if *license.Features.Cluster { props["EnableMetrics"] = strconv.FormatBool(*c.MetricsSettings.Enable) } - if *License.Features.GoogleOAuth { + if *license.Features.GoogleOAuth { props["EnableSignUpWithGoogle"] = strconv.FormatBool(c.GoogleSettings.Enable) } - if *License.Features.Office365OAuth { + if *license.Features.Office365OAuth { props["EnableSignUpWithOffice365"] = strconv.FormatBool(c.Office365Settings.Enable) } - if *License.Features.PasswordRequirements { + if *license.Features.PasswordRequirements { props["PasswordMinimumLength"] = fmt.Sprintf("%v", *c.PasswordSettings.MinimumLength) props["PasswordRequireLowercase"] = strconv.FormatBool(*c.PasswordSettings.Lowercase) props["PasswordRequireUppercase"] = strconv.FormatBool(*c.PasswordSettings.Uppercase) @@ -522,7 +521,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol) } - if *License.Features.Announcement { + if *license.Features.Announcement { props["EnableBanner"] = strconv.FormatBool(*c.AnnouncementSettings.EnableBanner) props["BannerText"] = *c.AnnouncementSettings.BannerText props["BannerColor"] = *c.AnnouncementSettings.BannerColor @@ -530,14 +529,14 @@ func GenerateClientConfig(c *model.Config, diagnosticId string) map[string]strin props["AllowBannerDismissal"] = strconv.FormatBool(*c.AnnouncementSettings.AllowBannerDismissal) } - if *License.Features.ThemeManagement { + if *license.Features.ThemeManagement { props["EnableThemeSelection"] = strconv.FormatBool(*c.ThemeSettings.EnableThemeSelection) props["DefaultTheme"] = *c.ThemeSettings.DefaultTheme props["AllowCustomThemes"] = strconv.FormatBool(*c.ThemeSettings.AllowCustomThemes) props["AllowedThemes"] = strings.Join(c.ThemeSettings.AllowedThemes, ",") } - if *License.Features.DataRetention { + if *license.Features.DataRetention { props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion) props["DataRetentionMessageRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.MessageRetentionDays), 10) props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion) diff --git a/utils/config_test.go b/utils/config_test.go index 9abc56d5ef..5809422f12 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -197,7 +197,7 @@ func TestGetClientConfig(t *testing.T) { cfg, _, err := LoadConfig("config.json") require.Nil(t, err) - configMap := GenerateClientConfig(cfg, "") + configMap := GenerateClientConfig(cfg, "", nil) if configMap["EmailNotificationContentsType"] != *cfg.EmailSettings.EmailNotificationContentsType { t.Fatal("EmailSettings.EmailNotificationContentsType not exposed to client config") } diff --git a/utils/file_backend.go b/utils/file_backend.go index c7a6c55912..42af7f604b 100644 --- a/utils/file_backend.go +++ b/utils/file_backend.go @@ -22,7 +22,7 @@ type FileBackend interface { RemoveDirectory(path string) *model.AppError } -func NewFileBackend(settings *model.FileSettings) (FileBackend, *model.AppError) { +func NewFileBackend(settings *model.FileSettings, enableComplianceFeatures bool) (FileBackend, *model.AppError) { switch *settings.DriverName { case model.IMAGE_DRIVER_S3: return &S3FileBackend{ @@ -33,7 +33,7 @@ func NewFileBackend(settings *model.FileSettings) (FileBackend, *model.AppError) signV2: settings.AmazonS3SignV2 != nil && *settings.AmazonS3SignV2, region: settings.AmazonS3Region, bucket: settings.AmazonS3Bucket, - encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && IsLicensed() && *License().Features.Compliance, + encrypt: settings.AmazonS3SSE != nil && *settings.AmazonS3SSE && enableComplianceFeatures, trace: settings.AmazonS3Trace != nil && *settings.AmazonS3Trace, }, nil case model.IMAGE_DRIVER_LOCAL: diff --git a/utils/file_backend_test.go b/utils/file_backend_test.go index 76cd1f4a81..46f75574ef 100644 --- a/utils/file_backend_test.go +++ b/utils/file_backend_test.go @@ -63,7 +63,7 @@ func TestS3FileBackendTestSuite(t *testing.T) { func (s *FileBackendTestSuite) SetupTest() { TranslationsPreInit() - backend, err := NewFileBackend(&s.settings) + backend, err := NewFileBackend(&s.settings, true) require.Nil(s.T(), err) s.backend = backend } diff --git a/utils/license.go b/utils/license.go index 2aaa2a5492..2853a58d00 100644 --- a/utils/license.go +++ b/utils/license.go @@ -5,28 +5,21 @@ package utils import ( "crypto" - "crypto/md5" "crypto/rsa" "crypto/sha512" "crypto/x509" "encoding/base64" "encoding/pem" - "fmt" "io/ioutil" "os" "strconv" "strings" - "sync/atomic" l4g "github.com/alecthomas/log4go" "github.com/mattermost/mattermost-server/model" ) -var isLicensedInt32 int32 -var licenseValue atomic.Value -var clientLicenseValue atomic.Value - var publicKey []byte = []byte(`-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyZmShlU8Z8HdG0IWSZ8r tSyzyxrXkJjsFUf0Ke7bm/TLtIggRdqOcUF3XEWqQk5RGD5vuq7Rlg1zZqMEBk8N @@ -37,92 +30,6 @@ a0v85XL6i9ote2P+fLZ3wX9EoioHzgdgB7arOxY50QRJO7OyCqpKFKv6lRWTXuSt hwIDAQAB -----END PUBLIC KEY-----`) -func init() { - SetLicense(nil) -} - -func IsLicensed() bool { - return atomic.LoadInt32(&isLicensedInt32) == 1 -} - -func SetIsLicensed(v bool) { - if v { - atomic.StoreInt32(&isLicensedInt32, 1) - } else { - atomic.StoreInt32(&isLicensedInt32, 0) - } -} - -func License() *model.License { - return licenseValue.Load().(*model.License) -} - -func SetClientLicense(m map[string]string) { - clientLicenseValue.Store(m) -} - -func ClientLicense() map[string]string { - return clientLicenseValue.Load().(map[string]string) -} - -func LoadLicense(licenseBytes []byte) { - if success, licenseStr := ValidateLicense(licenseBytes); success { - license := model.LicenseFromJson(strings.NewReader(licenseStr)) - SetLicense(license) - return - } - - l4g.Warn(T("utils.license.load_license.invalid.warn")) -} - -var licenseListeners = map[string]func(){} - -func AddLicenseListener(listener func()) string { - id := model.NewId() - licenseListeners[id] = listener - return id -} - -func RemoveLicenseListener(id string) { - delete(licenseListeners, id) -} - -func SetLicense(license *model.License) bool { - defer func() { - for _, listener := range licenseListeners { - listener() - } - }() - - if license == nil { - SetIsLicensed(false) - license = &model.License{ - Features: new(model.Features), - } - license.Features.SetDefaults() - licenseValue.Store(license) - - SetClientLicense(map[string]string{"IsLicensed": "false"}) - - return false - } else { - license.Features.SetDefaults() - - if !license.IsExpired() { - licenseValue.Store(license) - SetIsLicensed(true) - clientLicenseValue.Store(getClientLicense(license)) - return true - } - - return false - } -} - -func RemoveLicense() { - SetLicense(nil) -} - func ValidateLicense(signed []byte) (bool, string) { decoded := make([]byte, base64.StdEncoding.DecodedLen(len(signed))) @@ -213,12 +120,12 @@ func GetLicenseFileLocation(fileLocation string) string { } } -func getClientLicense(l *model.License) map[string]string { +func GetClientLicense(l *model.License) map[string]string { props := make(map[string]string) - props["IsLicensed"] = strconv.FormatBool(IsLicensed()) + props["IsLicensed"] = strconv.FormatBool(l != nil) - if IsLicensed() { + if l != nil { props["Id"] = l.Id props["Users"] = strconv.Itoa(*l.Features.Users) props["LDAP"] = strconv.FormatBool(*l.Features.LDAP) @@ -248,39 +155,3 @@ func getClientLicense(l *model.License) map[string]string { return props } - -func GetClientLicenseEtag(useSanitized bool) string { - value := "" - - lic := ClientLicense() - - if useSanitized { - lic = GetSanitizedClientLicense() - } - - for k, v := range lic { - value += fmt.Sprintf("%s:%s;", k, v) - } - - return model.Etag(fmt.Sprintf("%x", md5.Sum([]byte(value)))) -} - -func GetSanitizedClientLicense() map[string]string { - sanitizedLicense := make(map[string]string) - - for k, v := range ClientLicense() { - sanitizedLicense[k] = v - } - - if IsLicensed() { - delete(sanitizedLicense, "Id") - delete(sanitizedLicense, "Name") - delete(sanitizedLicense, "Email") - delete(sanitizedLicense, "PhoneNumber") - delete(sanitizedLicense, "IssuedAt") - delete(sanitizedLicense, "StartsAt") - delete(sanitizedLicense, "ExpiresAt") - } - - return sanitizedLicense -} diff --git a/utils/license_test.go b/utils/license_test.go index 9771ec4977..c2d1b4c05f 100644 --- a/utils/license_test.go +++ b/utils/license_test.go @@ -5,87 +5,20 @@ package utils import ( "testing" - - "github.com/mattermost/mattermost-server/model" ) -func TestSetLicense(t *testing.T) { - l1 := &model.License{} - l1.Features = &model.Features{} - l1.Customer = &model.Customer{} - l1.StartsAt = model.GetMillis() - 1000 - l1.ExpiresAt = model.GetMillis() + 100000 - if ok := SetLicense(l1); !ok { - t.Fatal("license should have worked") - } - - l2 := &model.License{} - l2.Features = &model.Features{} - l2.Customer = &model.Customer{} - l2.StartsAt = model.GetMillis() - 1000 - l2.ExpiresAt = model.GetMillis() - 100 - if ok := SetLicense(l2); ok { - t.Fatal("license should have failed") - } - - l3 := &model.License{} - l3.Features = &model.Features{} - l3.Customer = &model.Customer{} - l3.StartsAt = model.GetMillis() + 10000 - l3.ExpiresAt = model.GetMillis() + 100000 - if ok := SetLicense(l3); !ok { - t.Fatal("license should have passed") - } -} - func TestValidateLicense(t *testing.T) { b1 := []byte("junk") if ok, _ := ValidateLicense(b1); ok { t.Fatal("should have failed - bad license") } - LoadLicense(b1) - b2 := []byte("junkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunkjunk") if ok, _ := ValidateLicense(b2); ok { t.Fatal("should have failed - bad license") } } -func TestClientLicenseEtag(t *testing.T) { - etag1 := GetClientLicenseEtag(false) - - SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "true"}) - - etag2 := GetClientLicenseEtag(false) - if etag1 == etag2 { - t.Fatal("etags should not match") - } - - SetClientLicense(map[string]string{"SomeFeature": "true", "IsLicensed": "false"}) - - etag3 := GetClientLicenseEtag(false) - if etag2 == etag3 { - t.Fatal("etags should not match") - } -} - -func TestGetSanitizedClientLicense(t *testing.T) { - l1 := &model.License{} - l1.Features = &model.Features{} - l1.Customer = &model.Customer{} - l1.Customer.Name = "TestName" - l1.StartsAt = model.GetMillis() - 1000 - l1.ExpiresAt = model.GetMillis() + 100000 - SetLicense(l1) - - m := GetSanitizedClientLicense() - - if _, ok := m["Name"]; ok { - t.Fatal("should have been sanatized") - } -} - func TestGetLicenseFileLocation(t *testing.T) { fileName := GetLicenseFileLocation("") if len(fileName) == 0 { diff --git a/utils/mail.go b/utils/mail.go index 4c8a505af8..633f978181 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -105,17 +105,17 @@ func TestConnection(config *model.Config) { defer c.Close() } -func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config) *model.AppError { +func SendMailUsingConfig(to, subject, htmlBody string, config *model.Config, enableComplianceFeatures bool) *model.AppError { fromMail := mail.Address{Name: config.EmailSettings.FeedbackName, Address: config.EmailSettings.FeedbackEmail} - return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config) + return sendMail(to, to, fromMail, subject, htmlBody, nil, nil, config, enableComplianceFeatures) } // allows for sending an email with attachments and differing MIME/SMTP recipients -func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { - return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config) +func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { + return sendMail(mimeTo, smtpTo, from, subject, htmlBody, attachments, mimeHeaders, config, enableComplianceFeatures) } -func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config) *model.AppError { +func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, config *model.Config, enableComplianceFeatures bool) *model.AppError { if !config.EmailSettings.SendEmailNotifications || len(config.EmailSettings.SMTPServer) == 0 { return nil } @@ -151,7 +151,7 @@ func sendMail(mimeTo, smtpTo string, from mail.Address, subject, htmlBody string m.AddAlternative("text/html", htmlMessage) if attachments != nil { - fileBackend, err := NewFileBackend(&config.FileSettings) + fileBackend, err := NewFileBackend(&config.FileSettings, enableComplianceFeatures) if err != nil { return err } diff --git a/utils/mail_test.go b/utils/mail_test.go index 207fe32a53..7034204417 100644 --- a/utils/mail_test.go +++ b/utils/mail_test.go @@ -50,7 +50,7 @@ func TestSendMailUsingConfig(t *testing.T) { //Delete all the messages before check the sample email DeleteMailBox(emailTo) - if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg); err != nil { + if err := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true); err != nil { t.Log(err) t.Fatal("Should connect to the STMP Server") } else { @@ -95,7 +95,7 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { DeleteMailBox(smtpTo) // create a file that will be attached to the email - fileBackend, err := NewFileBackend(&cfg.FileSettings) + fileBackend, err := NewFileBackend(&cfg.FileSettings, true) assert.Nil(t, err) fileContents := []byte("hello world") fileName := "file.txt" @@ -111,7 +111,7 @@ func TestSendMailUsingConfigAdvanced(t *testing.T) { headers := make(map[string]string) headers["TestHeader"] = "TestValue" - if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg); err != nil { + if err := SendMailUsingConfigAdvanced(mimeTo, smtpTo, from, emailSubject, emailBody, attachments, headers, cfg, true); err != nil { t.Log(err) t.Fatal("Should connect to the STMP Server") } else { From 0daac7e4fc05ecb64dbc162daff618569bb249cd Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 9 Feb 2018 13:56:11 -0600 Subject: [PATCH 12/14] Add /v4/image api (#8230) * add image api * i suppose i should add a test... * only redirect to image proxy --- api4/api.go | 5 +++++ api4/image.go | 22 ++++++++++++++++++++ api4/image_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++ utils/config.go | 3 +++ 4 files changed, 82 insertions(+) create mode 100644 api4/image.go create mode 100644 api4/image_test.go diff --git a/api4/api.go b/api4/api.go index 580bd8c587..871dca0ac7 100644 --- a/api4/api.go +++ b/api4/api.go @@ -76,6 +76,8 @@ type Routes struct { Compliance *mux.Router // 'api/v4/compliance' Cluster *mux.Router // 'api/v4/cluster' + Image *mux.Router // 'api/v4/image' + LDAP *mux.Router // 'api/v4/ldap' Elasticsearch *mux.Router // 'api/v4/elasticsearch' @@ -194,6 +196,8 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.BaseRoutes.OpenGraph = api.BaseRoutes.ApiRoot.PathPrefix("/opengraph").Subrouter() + api.BaseRoutes.Image = api.BaseRoutes.ApiRoot.PathPrefix("/image").Subrouter() + api.InitUser() api.InitTeam() api.InitChannel() @@ -219,6 +223,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API { api.InitWebrtc() api.InitOpenGraph() api.InitPlugin() + api.InitImage() root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404)) diff --git a/api4/image.go b/api4/image.go new file mode 100644 index 0000000000..4589de2045 --- /dev/null +++ b/api4/image.go @@ -0,0 +1,22 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" +) + +func (api *API) InitImage() { + api.BaseRoutes.Image.Handle("", api.ApiSessionRequiredTrustRequester(getImage)).Methods("GET") +} + +func getImage(c *Context, w http.ResponseWriter, r *http.Request) { + // Only redirect to our image proxy if one is enabled. Arbitrary redirects are not allowed for + // security reasons. + if transform := c.App.ImageProxyAdder(); transform != nil { + http.Redirect(w, r, transform(r.URL.Query().Get("url")), http.StatusFound) + } else { + http.NotFound(w, r) + } +} diff --git a/api4/image_test.go b/api4/image_test.go new file mode 100644 index 0000000000..236d5785da --- /dev/null +++ b/api4/image_test.go @@ -0,0 +1,52 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" +) + +func TestGetImage(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + th.Client.HttpClient.CheckRedirect = func(*http.Request, []*http.Request) error { + return http.ErrUseLastResponse + } + + originURL := "http://foo.bar/baz.gif" + + r, err := http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+url.QueryEscape(originURL), nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = nil + }) + + resp, err := th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.ServiceSettings.ImageProxyType = model.NewString("willnorris/imageproxy") + cfg.ServiceSettings.ImageProxyURL = model.NewString("https://proxy.foo.bar") + }) + + r, err = http.NewRequest("GET", th.Client.ApiUrl+"/image?url="+originURL, nil) + require.NoError(t, err) + r.Header.Set(model.HEADER_AUTH, th.Client.AuthType+" "+th.Client.AuthToken) + + resp, err = th.Client.HttpClient.Do(r) + require.NoError(t, err) + assert.Equal(t, http.StatusFound, resp.StatusCode) + assert.Equal(t, "https://proxy.foo.bar//"+originURL, resp.Header.Get("Location")) +} diff --git a/utils/config.go b/utils/config.go index 0669d63e41..a855733a75 100644 --- a/utils/config.go +++ b/utils/config.go @@ -456,6 +456,9 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["PluginsEnabled"] = strconv.FormatBool(*c.PluginSettings.Enable) + hasImageProxy := c.ServiceSettings.ImageProxyType != nil && *c.ServiceSettings.ImageProxyType != "" && c.ServiceSettings.ImageProxyURL != nil && *c.ServiceSettings.ImageProxyURL != "" + props["HasImageProxy"] = strconv.FormatBool(hasImageProxy) + if license != nil { props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly) props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) From 3e0c3eff9f2ddec241cdb3f7a91230fd7c51a5f6 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 9 Feb 2018 17:47:22 -0500 Subject: [PATCH 13/14] ABC-228 Update GetPosts caching to work for non-60 limits (#8233) * Update GetPosts caching to work for non-60 limits * Only cache on limits of 30/60 and add test * Add comments clarifying 30 and 60 limits --- store/sqlstore/post_store.go | 15 ++++++++++----- store/storetest/post_store.go | 23 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index bc336e70d9..25c3c49139 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -322,7 +322,10 @@ type etagPosts struct { func (s SqlPostStore) InvalidateLastPostTimeCache(channelId string) { lastPostTimeCache.Remove(channelId) - lastPostsCache.Remove(channelId) + + // Keys are "{channelid}{limit}" and caching only occurs on limits of 30 and 60 + lastPostsCache.Remove(channelId + "30") + lastPostsCache.Remove(channelId + "60") } func (s SqlPostStore) GetEtag(channelId string, allowFromCache bool) store.StoreChannel { @@ -439,8 +442,9 @@ func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFro return } - if allowFromCache && offset == 0 && limit == 60 { - if cacheItem, ok := lastPostsCache.Get(channelId); ok { + // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients + if allowFromCache && offset == 0 && (limit == 60 || limit == 30) { + if cacheItem, ok := lastPostsCache.Get(fmt.Sprintf("%s%v", channelId, limit)); ok { if s.metrics != nil { s.metrics.IncrementMemCacheHitCounter("Last Posts Cache") } @@ -482,8 +486,9 @@ func (s SqlPostStore) GetPosts(channelId string, offset int, limit int, allowFro list.MakeNonNil() - if offset == 0 && limit == 60 { - lastPostsCache.AddWithExpiresInSecs(channelId, list, LAST_POSTS_CACHE_SEC) + // Caching only occurs on limits of 30 and 60, the common limits requested by MM clients + if offset == 0 && (limit == 60 || limit == 30) { + lastPostsCache.AddWithExpiresInSecs(fmt.Sprintf("%s%v", channelId, limit), list, LAST_POSTS_CACHE_SEC) } result.Data = list diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 4deb7f8d4e..e663d5a41f 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -27,7 +27,7 @@ func TestPostStore(t *testing.T, ss store.Store) { t.Run("PermDelete1Level", func(t *testing.T) { testPostStorePermDelete1Level(t, ss) }) t.Run("PermDelete1Level2", func(t *testing.T) { testPostStorePermDelete1Level2(t, ss) }) t.Run("GetWithChildren", func(t *testing.T) { testPostStoreGetWithChildren(t, ss) }) - t.Run("GetPostsWtihDetails", func(t *testing.T) { testPostStoreGetPostsWtihDetails(t, ss) }) + t.Run("GetPostsWithDetails", func(t *testing.T) { testPostStoreGetPostsWithDetails(t, ss) }) t.Run("GetPostsBeforeAfter", func(t *testing.T) { testPostStoreGetPostsBeforeAfter(t, ss) }) t.Run("GetPostsSince", func(t *testing.T) { testPostStoreGetPostsSince(t, ss) }) t.Run("Search", func(t *testing.T) { testPostStoreSearch(t, ss) }) @@ -490,7 +490,7 @@ func testPostStoreGetWithChildren(t *testing.T, ss store.Store) { } } -func testPostStoreGetPostsWtihDetails(t *testing.T, ss store.Store) { +func testPostStoreGetPostsWithDetails(t *testing.T, ss store.Store) { o1 := &model.Post{} o1.ChannelId = model.NewId() o1.UserId = model.NewId() @@ -591,6 +591,25 @@ func testPostStoreGetPostsWtihDetails(t *testing.T, ss store.Store) { if r2.Posts[o1.Id].Message != o1.Message { t.Fatal("Missing parent") } + + // Run once to fill cache + <-ss.Post().GetPosts(o1.ChannelId, 0, 30, true) + + o6 := &model.Post{} + o6.ChannelId = o1.ChannelId + o6.UserId = model.NewId() + o6.Message = "zz" + model.NewId() + "b" + o6 = (<-ss.Post().Save(o6)).Data.(*model.Post) + + // Should only be 6 since we hit the cache + r3 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList) + assert.Equal(t, 6, len(r3.Order)) + + ss.Post().InvalidateLastPostTimeCache(o1.ChannelId) + + // Cache was invalidated, we should get all the posts + r4 := (<-ss.Post().GetPosts(o1.ChannelId, 0, 30, true)).Data.(*model.PostList) + assert.Equal(t, 7, len(r4.Order)) } func testPostStoreGetPostsBeforeAfter(t *testing.T, ss store.Store) { From 07fd7aeeb8eb2b198b01b713a4ab57f6352faef2 Mon Sep 17 00:00:00 2001 From: Pierre de La Morinerie Date: Mon, 12 Feb 2018 22:16:32 +0530 Subject: [PATCH 14/14] Add tests for the `platform server` command (#8231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cleanup app state on initialization error When returning an initialization error, the app state was not cleaned up. This is especially visible during tests, as `appCount` is not decremented, and makes the new app initialization fail. * Test the `platform server` command As the `platform server` command only exits when interrupted by a signal, it is not possible to test it as the other cobra commands. Instead we directly test the actual command function. The internal command handler is slighly refactored to take a channel in argument, and registers it as the signal handler. Nothing very different—except than controlling this channel from the outside allows the test to send the system signal itself, thus preventing the server to run forever. --- app/app.go | 11 ++++-- cmd/platform/server.go | 10 +++--- cmd/platform/server_test.go | 72 +++++++++++++++++++++++++++++++++++++ jobs/jobs_watcher.go | 6 ++-- 4 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 cmd/platform/server_test.go diff --git a/app/app.go b/app/app.go index dd5deb342d..636f0a4285 100644 --- a/app/app.go +++ b/app/app.go @@ -86,7 +86,7 @@ var appCount = 0 // New creates a new App. You must call Shutdown when you're done with it. // XXX: For now, only one at a time is allowed as some resources are still shared. -func New(options ...Option) (*App, error) { +func New(options ...Option) (outApp *App, outErr error) { appCount++ if appCount > 1 { panic("Only one App should exist at a time. Did you forget to call Shutdown()?") @@ -103,6 +103,11 @@ func New(options ...Option) (*App, error) { clientConfig: make(map[string]string), licenseListeners: map[string]func(){}, } + defer func() { + if outErr != nil { + app.Shutdown() + } + }() for _, option := range options { option(app) @@ -182,7 +187,9 @@ func (a *App) Shutdown() { a.ShutDownPlugins() a.WaitForGoroutines() - a.Srv.Store.Close() + if a.Srv.Store != nil { + a.Srv.Store.Close() + } a.Srv = nil if a.htmlTemplateWatcher != nil { diff --git a/cmd/platform/server.go b/cmd/platform/server.go index a8a6e8923f..1b411cf202 100644 --- a/cmd/platform/server.go +++ b/cmd/platform/server.go @@ -42,10 +42,11 @@ func runServerCmd(cmd *cobra.Command, args []string) error { disableConfigWatch, _ := cmd.Flags().GetBool("disableconfigwatch") - return runServer(config, disableConfigWatch) + interruptChan := make(chan os.Signal, 1) + return runServer(config, disableConfigWatch, interruptChan) } -func runServer(configFileLocation string, disableConfigWatch bool) error { +func runServer(configFileLocation string, disableConfigWatch bool, interruptChan chan os.Signal) error { options := []app.Option{app.ConfigFile(configFileLocation)} if disableConfigWatch { options = append(options, app.DisableConfigWatch) @@ -165,9 +166,8 @@ func runServer(configFileLocation string, disableConfigWatch bool) error { // wait for kill signal before attempting to gracefully shutdown // the running service - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - <-c + signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + <-interruptChan if a.Cluster != nil { a.Cluster.StopInterNodeCommunication() diff --git a/cmd/platform/server_test.go b/cmd/platform/server_test.go new file mode 100644 index 0000000000..15f9a357a3 --- /dev/null +++ b/cmd/platform/server_test.go @@ -0,0 +1,72 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package main + +import ( + "io/ioutil" + "os" + "syscall" + "testing" + + "github.com/mattermost/mattermost-server/jobs" + "github.com/mattermost/mattermost-server/utils" + "github.com/stretchr/testify/require" +) + +type ServerTestHelper struct { + configPath string + disableConfigWatch bool + interruptChan chan os.Signal + originalInterval int +} + +func SetupServerTest() *ServerTestHelper { + // Build a channel that will be used by the server to receive system signals… + interruptChan := make(chan os.Signal, 1) + // …and sent it immediately a SIGINT value. + // This will make the server loop stop as soon as it started successfully. + interruptChan <- syscall.SIGINT + + // Let jobs poll for termination every 0.2s (instead of every 15s by default) + // Otherwise we would have to wait the whole polling duration before the test + // terminates. + originalInterval := jobs.DEFAULT_WATCHER_POLLING_INTERVAL + jobs.DEFAULT_WATCHER_POLLING_INTERVAL = 200 + + th := &ServerTestHelper{ + configPath: utils.FindConfigFile("config.json"), + disableConfigWatch: true, + interruptChan: interruptChan, + originalInterval: originalInterval, + } + return th +} + +func (th *ServerTestHelper) TearDownServerTest() { + jobs.DEFAULT_WATCHER_POLLING_INTERVAL = th.originalInterval +} + +func TestRunServerSuccess(t *testing.T) { + th := SetupServerTest() + defer th.TearDownServerTest() + + err := runServer(th.configPath, th.disableConfigWatch, th.interruptChan) + require.NoError(t, err) +} + +func TestRunServerInvalidConfigFile(t *testing.T) { + th := SetupServerTest() + defer th.TearDownServerTest() + + // Start the server with an unreadable config file + unreadableConfigFile, err := ioutil.TempFile("", "mattermost-unreadable-config-file-") + if err != nil { + panic(err) + } + os.Chmod(unreadableConfigFile.Name(), 0200) + defer os.Remove(unreadableConfigFile.Name()) + + err = runServer(unreadableConfigFile.Name(), th.disableConfigWatch, th.interruptChan) + require.Error(t, err) +} diff --git a/jobs/jobs_watcher.go b/jobs/jobs_watcher.go index f519e7ccae..eaa3a4e737 100644 --- a/jobs/jobs_watcher.go +++ b/jobs/jobs_watcher.go @@ -11,9 +11,9 @@ import ( "github.com/mattermost/mattermost-server/model" ) -const ( - DEFAULT_WATCHER_POLLING_INTERVAL = 15000 -) +// Default polling interval for jobs termination. +// (Defining as `var` rather than `const` allows tests to lower the interval.) +var DEFAULT_WATCHER_POLLING_INTERVAL = 15000 type Watcher struct { srv *JobServer