From a92c830e570fac8378c96dc47054daa461539d37 Mon Sep 17 00:00:00 2001 From: Daniel Schalla Date: Tue, 27 Aug 2019 10:39:01 +0200 Subject: [PATCH 01/47] Add additional input validation for user creation (#11937) --- api4/user.go | 2 ++ api4/user_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++ model/user.go | 12 +++++++++ 3 files changed, 78 insertions(+) diff --git a/api4/user.go b/api4/user.go index e4589ae85f..927971c4f2 100644 --- a/api4/user.go +++ b/api4/user.go @@ -86,6 +86,8 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { return } + user.SanitizeInput() + tokenId := r.URL.Query().Get("t") inviteId := r.URL.Query().Get("iid") diff --git a/api4/user_test.go b/api4/user_test.go index 07fff6a122..505c654b1e 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -83,6 +83,70 @@ func TestCreateUser(t *testing.T) { assert.Equal(t, http.StatusBadRequest, r.StatusCode) } +func TestCreateUserInputFilter(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + t.Run("DomainRestriction", func(t *testing.T) { + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.EnableOpenServer = true + *cfg.TeamSettings.EnableUserCreation = true + *cfg.TeamSettings.RestrictCreationToDomains = "mattermost.com" + }) + + defer th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.RestrictCreationToDomains = "" + }) + + t.Run("ValidUser", func(t *testing.T) { + user := &model.User{Email: "foobar+testdomainrestriction@mattermost.com", Password: "Password1", Username: GenerateTestUsername()} + _, resp := th.SystemAdminClient.CreateUser(user) + CheckNoError(t, resp) + }) + + t.Run("InvalidEmail", func(t *testing.T) { + user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername()} + _, resp := th.SystemAdminClient.CreateUser(user) + CheckBadRequestStatus(t, resp) + }) + + t.Run("AuthServiceFilter", func(t *testing.T) { + user := &model.User{Email: "foobar+testdomainrestriction@mattermost.org", Password: "Password1", Username: GenerateTestUsername(), AuthService: "ldap"} + _, resp := th.SystemAdminClient.CreateUser(user) + CheckBadRequestStatus(t, resp) + }) + }) + + t.Run("Roles", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.EnableOpenServer = true + *cfg.TeamSettings.EnableUserCreation = true + *cfg.TeamSettings.RestrictCreationToDomains = "" + }) + + t.Run("InvalidRole", func(t *testing.T) { + user := &model.User{Email: "foobar+testinvalidrole@mattermost.com", Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"} + _, resp := th.SystemAdminClient.CreateUser(user) + CheckNoError(t, resp) + ruser, err := th.App.GetUserByEmail("foobar+testinvalidrole@mattermost.com") + assert.Nil(t, err) + assert.NotEqual(t, ruser.Roles, "system_user system_admin") + }) + }) + + t.Run("InvalidId", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.TeamSettings.EnableOpenServer = true + *cfg.TeamSettings.EnableUserCreation = true + }) + + user := &model.User{Id: "AAAAAAAAAAAAAAAAAAAAAAAAAA", Email: "foobar+testinvalidid@mattermost.com", Password: "Password1", Username: GenerateTestUsername(), Roles: "system_user system_admin"} + _, resp := th.SystemAdminClient.CreateUser(user) + CheckBadRequestStatus(t, resp) + }) +} + func TestCreateUserWithToken(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/model/user.go b/model/user.go index db0621e04e..ee8c8e7b6b 100644 --- a/model/user.go +++ b/model/user.go @@ -494,6 +494,18 @@ func (u *User) Sanitize(options map[string]bool) { } } +// Remove any input data from the user object that is not user controlled +func (u *User) SanitizeInput() { + u.AuthData = NewString("") + u.AuthService = "" + u.LastPasswordUpdate = 0 + u.LastPictureUpdate = 0 + u.FailedAttempts = 0 + u.EmailVerified = false + u.MfaActive = false + u.MfaSecret = "" +} + func (u *User) ClearNonProfileFields() { u.Password = "" u.AuthData = NewString("") From b91824328a196b82b1d0a0accda96bcebb52ad38 Mon Sep 17 00:00:00 2001 From: Jason Blais <13119842+jasonblais@users.noreply.github.com> Date: Tue, 27 Aug 2019 16:26:45 -0400 Subject: [PATCH 02/47] Reorder re-packaged plugins to be alphabetical in diagnostics (#11974) --- app/diagnostics.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/diagnostics.go b/app/diagnostics.go index 4296c1e01e..98a8f4dece 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -586,13 +586,13 @@ func (a *App) trackConfig() { }) a.SendDiagnostic(TRACK_CONFIG_PLUGIN, map[string]interface{}{ - "enable_gitlab": pluginActivated(cfg.PluginSettings.PluginStates, "com.github.manland.mattermost-plugin-gitlab"), "enable_antivirus": pluginActivated(cfg.PluginSettings.PluginStates, "antivirus"), - "enable_jenkins": pluginActivated(cfg.PluginSettings.PluginStates, "jenkins"), "enable_autolink": pluginActivated(cfg.PluginSettings.PluginStates, "mattermost-autolink"), "enable_aws_sns": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.aws-sns"), "enable_custom_user_attributes": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.custom-attributes"), "enable_github": pluginActivated(cfg.PluginSettings.PluginStates, "github"), + "enable_gitlab": pluginActivated(cfg.PluginSettings.PluginStates, "com.github.manland.mattermost-plugin-gitlab"), + "enable_jenkins": pluginActivated(cfg.PluginSettings.PluginStates, "jenkins"), "enable_jira": pluginActivated(cfg.PluginSettings.PluginStates, "jira"), "enable_nps": pluginActivated(cfg.PluginSettings.PluginStates, "com.mattermost.nps"), "enable_nps_survey": pluginSetting(&cfg.PluginSettings, "com.mattermost.nps", "enablesurvey", true), From 2980d341d482093aaf847ee5c24cfd95b8d3b3f4 Mon Sep 17 00:00:00 2001 From: Micah Thompson Date: Thu, 29 Aug 2019 06:09:09 -0400 Subject: [PATCH 03/47] MM-11102 Show channel links in notification emails (#11670) * Added code to hyperlink channels in HTML email Adjusted imports * Rending channel display name in hyperlink instead of channel URL handler Addressed go formatting warning * [MM-11102] Channel links should show as links in notification emails * Escaped post;ChannelMentions, GetChannelsByName functions; Changed tests * remove mlog.info * MultiMention and Escape tests; Change logging --- app/notification_email.go | 36 ++++++- app/notification_email_test.go | 176 +++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) diff --git a/app/notification_email.go b/app/notification_email.go index 07fba3449e..004ee085bc 100644 --- a/app/notification_email.go +++ b/app/notification_email.go @@ -6,6 +6,7 @@ package app import ( "fmt" "html" + "html/template" "net/url" "path/filepath" "strings" @@ -171,7 +172,10 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, var bodyPage *utils.HTMLTemplate if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL { bodyPage = a.NewEmailTemplate("post_body_full", recipient.Locale) - bodyPage.Props["PostMessage"] = a.GetMessageForNotification(post, translateFunc) + postMessage := a.GetMessageForNotification(post, translateFunc) + postMessage = html.EscapeString(postMessage) + normalizedPostMessage := a.generateHyperlinkForChannels(postMessage, teamName, teamURL) + bodyPage.Props["PostMessage"] = template.HTML(normalizedPostMessage) } else { bodyPage = a.NewEmailTemplate("post_body_generic", recipient.Locale) } @@ -283,6 +287,36 @@ func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bo } } +func (a *App) generateHyperlinkForChannels(postMessage, teamName, teamURL string) string { + team, err := a.GetTeamByName(teamName) + if err != nil { + mlog.Error("Encountered error while looking up team by name", mlog.String("Team Name", teamName), mlog.Err(err)) + return postMessage + } + + channelNames := model.ChannelMentions(postMessage) + if len(channelNames) == 0 { + return postMessage + } + + channels, err := a.GetChannelsByNames(channelNames, team.Id) + if err != nil { + mlog.Error("Encountered error while getting channels", mlog.Err(err)) + return postMessage + } + + visited := make(map[string]bool) + for _, ch := range channels { + if !visited[ch.Id] && ch.Type == model.CHANNEL_OPEN { + channelURL := teamURL + "/channels/" + ch.Name + channelHyperLink := fmt.Sprintf("%s", channelURL, "~"+ch.Name) + postMessage = strings.Replace(postMessage, "~"+ch.Name, channelHyperLink, -1) + visited[ch.Id] = true + } + } + return postMessage +} + func (a *App) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string { if len(strings.TrimSpace(post.Message)) != 0 || len(post.FileIds) == 0 { return post.Message diff --git a/app/notification_email_test.go b/app/notification_email_test.go index 1c920a9b0a..267694c2a1 100644 --- a/app/notification_email_test.go +++ b/app/notification_email_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/services/timezones" "github.com/mattermost/mattermost-server/utils" @@ -499,3 +501,177 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T) t.Fatal("Expected email text '" + teamURL + "'. Got " + body) } } + +func TestGetNotificationEmailEscapingChars(t *testing.T) { + th := Setup(t) + defer th.TearDown() + + ch := &model.Channel{ + DisplayName: "ChannelName", + Type: model.CHANNEL_OPEN, + } + channelName := "ChannelName" + recipient := &model.User{} + message := "Bold Test" + post := &model.Post{ + Message: message, + } + + senderName := "sender" + teamName := "team" + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, ch, + channelName, senderName, teamName, teamURL, + emailNotificationContentsType, true, translateFunc) + + fmt.Println(body) + assert.NotContains(t, body, message) +} + +func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.BasicChannel + recipient := th.BasicUser2 + post := &model.Post{ + Message: "This is the message ~" + ch.Name, + } + + senderName := th.BasicUser.Username + teamName := th.BasicTeam.Name + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, ch, + ch.Name, senderName, teamName, teamURL, + emailNotificationContentsType, true, translateFunc) + channelURL := teamURL + "/channels/" + ch.Name + mention := "~" + ch.Name + assert.Contains(t, body, ""+mention+"") +} + +func TestGetNotificationEmailBodyMultiPublicChannelMention(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.BasicChannel + mention := "~" + ch.Name + + ch2 := th.CreateChannel(th.BasicTeam) + mention2 := "~" + ch2.Name + + ch3 := th.CreateChannel(th.BasicTeam) + mention3 := "~" + ch3.Name + + message := fmt.Sprintf("This is the message Channel1: %s; Channel2: %s;"+ + " Channel3: %s", mention, mention2, mention3) + recipient := th.BasicUser2 + post := &model.Post{ + Message: message, + } + + senderName := th.BasicUser.Username + teamName := th.BasicTeam.Name + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, ch, + ch.Name, senderName, teamName, teamURL, + emailNotificationContentsType, true, translateFunc) + channelURL := teamURL + "/channels/" + ch.Name + channelURL2 := teamURL + "/channels/" + ch2.Name + channelURL3 := teamURL + "/channels/" + ch3.Name + expMessage := fmt.Sprintf("This is the message Channel1: %s;"+ + " Channel2: %s; Channel3: %s", + channelURL, mention, channelURL2, mention2, channelURL3, mention3) + assert.Contains(t, body, expMessage) +} + +func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.CreatePrivateChannel(th.BasicTeam) + recipient := th.BasicUser2 + post := &model.Post{ + Message: "This is the message ~" + ch.Name, + } + + senderName := th.BasicUser.Username + teamName := ch.Name + teamURL := "http://localhost:8065/" + teamName + emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL + translateFunc := utils.GetUserTranslations("en") + + body := th.App.getNotificationEmailBody(recipient, post, ch, + ch.Name, senderName, teamName, teamURL, + emailNotificationContentsType, true, translateFunc) + channelURL := teamURL + "/channels/" + ch.Name + mention := "~" + ch.Name + assert.NotContains(t, body, ""+mention+"") +} + +func TestGenerateHyperlinkForChannelsPublic(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.BasicChannel + message := "This is the message " + mention := "~" + ch.Name + + teamName := th.BasicTeam.Name + teamURL := "http://localhost:8065/" + teamName + + outMessage := th.App.generateHyperlinkForChannels(message+mention, teamName, teamURL) + channelURL := teamURL + "/channels/" + ch.Name + assert.Equal(t, message+""+mention+"", outMessage) +} + +func TestGenerateHyperlinkForChannelsMultiPublic(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.BasicChannel + mention := "~" + ch.Name + + ch2 := th.CreateChannel(th.BasicTeam) + mention2 := "~" + ch2.Name + + ch3 := th.CreateChannel(th.BasicTeam) + mention3 := "~" + ch3.Name + + message := fmt.Sprintf("This is the message Channel1: %s; Channel2: %s;"+ + " Channel3: %s", mention, mention2, mention3) + + teamName := th.BasicTeam.Name + teamURL := "http://localhost:8065/" + teamName + + outMessage := th.App.generateHyperlinkForChannels(message, teamName, teamURL) + channelURL := teamURL + "/channels/" + ch.Name + channelURL2 := teamURL + "/channels/" + ch2.Name + channelURL3 := teamURL + "/channels/" + ch3.Name + expMessage := fmt.Sprintf("This is the message Channel1: %s;"+ + " Channel2: %s; Channel3: %s", + channelURL, mention, channelURL2, mention2, channelURL3, mention3) + assert.Equal(t, expMessage, outMessage) +} + +func TestGenerateHyperlinkForChannelsPrivate(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + ch := th.CreatePrivateChannel(th.BasicTeam) + message := "This is the message ~" + ch.Name + + teamName := th.BasicTeam.Name + teamURL := "http://localhost:8065/" + teamName + + outMessage := th.App.generateHyperlinkForChannels(message, teamName, teamURL) + assert.Equal(t, message, outMessage) +} From 9e9f1bb2aaa74bc1850483ffc5696a0a38e9b7fb Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Thu, 29 Aug 2019 20:36:38 +0200 Subject: [PATCH 04/47] Stop plugin healthchecks before shutting down plugins (#11972) * cancel plugin check when shutdown * move to top * stop healthcheck job only once, and if started * whitespace --- plugin/environment.go | 4 ++++ plugin/health_check.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugin/environment.go b/plugin/environment.go index d301a8942e..a6345579d6 100644 --- a/plugin/environment.go +++ b/plugin/environment.go @@ -284,6 +284,10 @@ func (env *Environment) RestartPlugin(id string) error { // Shutdown deactivates all plugins and gracefully shuts down the environment. func (env *Environment) Shutdown() { + if env.pluginHealthCheckJob != nil { + env.pluginHealthCheckJob.Cancel() + } + env.registeredPlugins.Range(func(key, value interface{}) bool { rp := value.(*registeredPlugin) diff --git a/plugin/health_check.go b/plugin/health_check.go index 7f9855e125..85989e4775 100644 --- a/plugin/health_check.go +++ b/plugin/health_check.go @@ -5,6 +5,7 @@ package plugin import ( "fmt" + "sync" "time" "github.com/mattermost/mattermost-server/mlog" @@ -19,9 +20,10 @@ const ( ) type PluginHealthCheckJob struct { - cancel chan struct{} - cancelled chan struct{} - env *Environment + cancel chan struct{} + cancelled chan struct{} + cancelOnce sync.Once + env *Environment } // InitPluginHealthCheckJob starts a new job if one is not running and is set to enabled, or kills an existing one if set to disabled. @@ -125,7 +127,9 @@ func newPluginHealthCheckJob(env *Environment) *PluginHealthCheckJob { } func (job *PluginHealthCheckJob) Cancel() { - close(job.cancel) + job.cancelOnce.Do(func() { + close(job.cancel) + }) <-job.cancelled } From 6aa7442e43301f828a709c0c3e74c1d8da0dfcdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 29 Aug 2019 22:11:45 +0200 Subject: [PATCH 05/47] Changing default smtp port to use the new inbucket port as default (#11965) --- model/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/config.go b/model/config.go index b64a2f3215..5236937076 100644 --- a/model/config.go +++ b/model/config.go @@ -1231,7 +1231,7 @@ func (s *EmailSettings) SetDefaults(isUpdate bool) { } if s.SMTPPort == nil || len(*s.SMTPPort) == 0 { - s.SMTPPort = NewString("2500") + s.SMTPPort = NewString("10025") } if s.ConnectionSecurity == nil || *s.ConnectionSecurity == CONN_SECURITY_PLAIN { From 782375f5c71172607a8eb10962d7a6f0e90312cf Mon Sep 17 00:00:00 2001 From: threepwood-mm <54032291+threepwood-mm@users.noreply.github.com> Date: Thu, 29 Aug 2019 22:44:13 +0200 Subject: [PATCH 06/47] [MM-17703] Diff config files (#11898) --- Makefile | 5 +++- scripts/diff-config.sh | 53 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100755 scripts/diff-config.sh diff --git a/Makefile b/Makefile index da4c7f9b91..9598d36009 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build package run stop run-client run-server stop-client stop-server restart restart-server restart-client start-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist prepare-enteprise run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows internal-test-web-client vet run-server-for-web-client-tests +.PHONY: build package run stop run-client run-server stop-client stop-server restart restart-server restart-client start-docker clean-dist clean nuke check-style check-client-style check-server-style check-unit-tests test dist prepare-enteprise run-client-tests setup-run-client-tests cleanup-run-client-tests test-client build-linux build-osx build-windows internal-test-web-client vet run-server-for-web-client-tests diff-config ROOT := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) @@ -410,6 +410,9 @@ config-reset: ## Resets the config/config.json file to the default. rm -f config/config.json OUTPUT_CONFIG=$(PWD)/config/config.json go generate ./config +diff-config: ## Compares default configuration between two mattermost versions + @./scripts/diff-config.sh + clean: stop-docker ## Clean up everything except persistant server data. @echo Cleaning diff --git a/scripts/diff-config.sh b/scripts/diff-config.sh new file mode 100755 index 0000000000..6eb45c5120 --- /dev/null +++ b/scripts/diff-config.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +jq_cmd=jq +[[ $(type -P "$jq_cmd") ]] || { + echo "'$jq_cmd' command line JSON processor not found"; + echo "Please install on linux with 'sudo apt-get install jq'" + echo "Please install on mac with 'brew install jq'" + exit 1; +} + +if [ -z "$FROM" ] +then + echo "Missing FROM version. Usage: make diff-config FROM=1.1.1 TO=2.2.2" + exit 1 +fi + +if [ -z "$TO" ] +then + echo "Missing TO version. Usage: make diff-config FROM=1.1.1 TO=2.2.2" + exit 1 +fi + +# Returns the config file for a specific release +fetch_config() { + local url="https://releases.mattermost.com/$1/mattermost-$1-linux-amd64.tar.gz" + curl -sf "$url" | tar -xzOf - mattermost/config/config.json | jq -S . +} + +echo Fetching config files +from_config="$(fetch_config "$FROM")" +if [ -z "$from_config" ] +then + echo Invalid version "$FROM" + exit 1 +fi + +to_config=$(fetch_config "$TO") +if [ -z "$to_config" ] +then + echo Invalid version "$TO" + exit 1 +fi + +echo Comparing config files +diff -y <(echo "$from_config") <(echo "$to_config") + +# We ignore exits with 1 since it just means there's a difference, which is fine for us. +diff_exit=$? +if [ $diff_exit -eq 1 ]; then + exit 0 +else + exit $diff_exit +fi From 0cd3663d919570fbe6465d71fcfabbe0ff08533f Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Fri, 30 Aug 2019 06:49:31 +0900 Subject: [PATCH 07/47] [MM-17300] Do not add empty string as a keyword key (#11961) * Do not add empty string as a keyword key * Add unit test * User assert lib over t.Fatal --- app/notification.go | 4 +++- app/notification_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/notification.go b/app/notification.go index 1d4a2ccb49..46d7a0db3c 100644 --- a/app/notification.go +++ b/app/notification.go @@ -593,7 +593,9 @@ func (a *App) getMentionKeywordsInChannel(profiles map[string]*model.User, lookF for _, k := range splitKeys { // note that these are made lower case so that we can do a case insensitive check for them key := strings.ToLower(k) - keywords[key] = append(keywords[key], id) + if key != "" { + keywords[key] = append(keywords[key], id) + } } } diff --git a/app/notification_test.go b/app/notification_test.go index 744b9738d4..751e14acbc 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -1150,6 +1150,29 @@ func TestGetMentionKeywords(t *testing.T) { } else if _, ok := mentions["@here"]; ok { t.Fatal("should not have mentioned any user with @here") } + + // user with empty mention keys + userNoMentionKeys := &model.User{ + Id: model.NewId(), + FirstName: "First", + Username: "User", + NotifyProps: map[string]string{ + "mention_keys": ",", + }, + } + + channelMemberNotifyPropsMapEmptyOff := map[string]model.StringMap{ + userNoMentionKeys.Id: { + "ignore_channel_mentions": model.IGNORE_CHANNEL_MENTIONS_OFF, + }, + } + + profiles = map[string]*model.User{userNoMentionKeys.Id: userNoMentionKeys} + mentions = th.App.getMentionKeywordsInChannel(profiles, true, channelMemberNotifyPropsMapEmptyOff) + assert.Equal(t, 1, len(mentions), "should've returned one metion keyword") + ids, ok := mentions["@user"] + assert.True(t, ok) + assert.Equal(t, userNoMentionKeys.Id, ids[0], "should've returned mention key of @user") } func TestGetMentionsEnabledFields(t *testing.T) { From d1f0216c2240c6c8b80ddf23e989a808ab0a79d8 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 30 Aug 2019 11:14:01 +0900 Subject: [PATCH 08/47] Remove @ sign from app.notification.subject.direct.full translation (#11990) * Remove @ sign from app.notification.subject.direct.full translation * Fix uni test --- app/notification_email_test.go | 2 +- i18n/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/notification_email_test.go b/app/notification_email_test.go index 267694c2a1..7dcf1ff874 100644 --- a/app/notification_email_test.go +++ b/app/notification_email_test.go @@ -27,7 +27,7 @@ func TestGetDirectMessageNotificationEmailSubject(t *testing.T) { CreateAt: 1501804801000, } translateFunc := utils.GetUserTranslations("en") - subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "sender", true) + subject := getDirectMessageNotificationEmailSubject(user, post, translateFunc, "http://localhost:8065", "@sender", true) if !strings.HasPrefix(subject, expectedPrefix) { t.Fatal("Expected subject line prefix '" + expectedPrefix + "', got " + subject) } diff --git a/i18n/en.json b/i18n/en.json index a50018668c..af1be7e046 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3424,7 +3424,7 @@ }, { "id": "app.notification.subject.direct.full", - "translation": "[{{.SiteName}}] New Direct Message from @{{.SenderDisplayName}} on {{.Month}} {{.Day}}, {{.Year}}" + "translation": "[{{.SiteName}}] New Direct Message from {{.SenderDisplayName}} on {{.Month}} {{.Day}}, {{.Year}}" }, { "id": "app.notification.subject.group_message.full", From e1eb83963619111dbbd10a97551432112dfce3bc Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Fri, 30 Aug 2019 14:44:55 +0200 Subject: [PATCH 09/47] Add integrity check command to CLI (#11599) * Add integrity command * Add structures and implementation for basic referential integrity check * Use a channel to receive integrity check reports as they generates * Setup unit testing * Add confirm prompt to integrity command and make verbose output optional * Add more integrity checks * Use wrapper functions to simplify behaviour and tests * Improve extensibility of IntegrityCheckResult * Improve CheckIntegrity tests performance * Use a config structure for relational integrity checks * Add more relational integrity checks * Add more checks and do some cleanup * Add more relational integrity checks with proper tests * Fix tests to use sync functions * Add more info to integrity command help * Add more relational integrity checks * Add more relational integrity checks * Add missing checks * Show more information about missing records * Fix to use new sync function * Change integrity check functions to accept a SqlSupplier * Fix code duplication * Use squirrel for query building --- cmd/mattermost/commands/integrity.go | 78 + store/layered_store.go | 4 + store/sqlstore/integrity.go | 514 ++++++ store/sqlstore/integrity_test.go | 1539 +++++++++++++++++ store/sqlstore/supplier.go | 6 + store/store.go | 19 + .../mocks/LayeredStoreDatabaseLayer.go | 16 + store/storetest/mocks/Store.go | 16 + store/storetest/store.go | 3 + 9 files changed, 2195 insertions(+) create mode 100644 cmd/mattermost/commands/integrity.go create mode 100644 store/sqlstore/integrity.go create mode 100644 store/sqlstore/integrity_test.go diff --git a/cmd/mattermost/commands/integrity.go b/cmd/mattermost/commands/integrity.go new file mode 100644 index 0000000000..5f61aa4476 --- /dev/null +++ b/cmd/mattermost/commands/integrity.go @@ -0,0 +1,78 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package commands + +import ( + "fmt" + "os" + "strings" + + "github.com/mattermost/mattermost-server/store" + "github.com/spf13/cobra" +) + +var IntegrityCmd = &cobra.Command{ + Use: "integrity", + Short: "Check database data integrity", + RunE: integrityCmdF, +} + +func init() { + IntegrityCmd.Flags().Bool("confirm", false, "Confirm you really want to run a complete integrity check that may temporarily harm system performance") + IntegrityCmd.Flags().BoolP("verbose", "v", false, "Show detailed information on integrity check results") + RootCmd.AddCommand(IntegrityCmd) +} + +func printRelationalIntegrityCheckResult(data store.RelationalIntegrityCheckData, verbose bool) { + fmt.Println(fmt.Sprintf("Found %d records in relation %s orphans of relation %s", + len(data.Records), data.ChildName, data.ParentName)) + if !verbose { + return + } + for _, record := range data.Records { + if record.ChildId != "" { + fmt.Println(fmt.Sprintf(" Child %s (%s.%s) is missing Parent %s (%s.%s)", record.ChildId, data.ChildName, data.ChildIdAttr, record.ParentId, data.ChildName, data.ParentIdAttr)) + } else { + fmt.Println(fmt.Sprintf(" Child is missing Parent %s (%s.%s)", record.ParentId, data.ChildName, data.ParentIdAttr)) + } + } +} + +func printIntegrityCheckResult(result store.IntegrityCheckResult, verbose bool) { + switch data := result.Data.(type) { + case store.RelationalIntegrityCheckData: + printRelationalIntegrityCheckResult(data, verbose) + } +} + +func integrityCmdF(command *cobra.Command, args []string) error { + a, err := InitDBCommandContextCobra(command) + if err != nil { + return err + } + defer a.Shutdown() + + confirmFlag, _ := command.Flags().GetBool("confirm") + if !confirmFlag { + var confirm string + fmt.Fprintf(os.Stdout, "This check may harm performance on live systems. Are you sure you want to proceed? (y/N): ") + fmt.Scanln(&confirm) + if !strings.EqualFold(confirm, "y") && !strings.EqualFold(confirm, "yes") { + fmt.Fprintf(os.Stderr, "Aborted.\n") + return nil + } + } + + verboseFlag, _ := command.Flags().GetBool("verbose") + results := a.Srv.Store.CheckIntegrity() + for result := range results { + if result.Err != nil { + fmt.Fprintf(os.Stderr, "%s\n", result.Err.Error()) + break + } + printIntegrityCheckResult(result, verboseFlag) + } + + return nil +} diff --git a/store/layered_store.go b/store/layered_store.go index 26c8f0c9f2..9d9f3c9a47 100644 --- a/store/layered_store.go +++ b/store/layered_store.go @@ -217,6 +217,10 @@ func (s *LayeredStore) TotalSearchDbConnections() int { return s.DatabaseLayer.TotalSearchDbConnections() } +func (s *LayeredStore) CheckIntegrity() <-chan IntegrityCheckResult { + return s.DatabaseLayer.CheckIntegrity() +} + type LayeredRoleStore struct { *LayeredStore } diff --git a/store/sqlstore/integrity.go b/store/sqlstore/integrity.go new file mode 100644 index 0000000000..a09d2ec1c3 --- /dev/null +++ b/store/sqlstore/integrity.go @@ -0,0 +1,514 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package sqlstore + +import ( + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/store" + + sq "github.com/Masterminds/squirrel" +) + +type relationalCheckConfig struct { + parentName string + parentIdAttr string + childName string + childIdAttr string + canParentIdBeEmpty bool + sortRecords bool +} + +func getOrphanedRecords(ss *SqlSupplier, cfg relationalCheckConfig) ([]store.OrphanedRecord, error) { + var records []store.OrphanedRecord + + sub := ss.getQueryBuilder(). + Select("TRUE"). + From(cfg.parentName). + Prefix("NOT EXISTS ("). + Suffix(")"). + Where(sq.Eq{"id": cfg.childName + "." + cfg.parentIdAttr}) + + main := ss.getQueryBuilder(). + Select(). + Column(cfg.parentIdAttr + " AS ParentId"). + From(cfg.childName). + Where(sub) + + if cfg.childIdAttr != "" { + main = main.Column(cfg.childIdAttr + " AS ChildId") + } + + if cfg.canParentIdBeEmpty { + main = main.Where(sq.NotEq{cfg.parentIdAttr: ""}) + } + + if cfg.sortRecords { + main = main.OrderBy(cfg.parentIdAttr) + } + + query, args, _ := main.ToSql() + _, err := ss.GetMaster().Select(&records, query, args...) + + return records, err +} + +func checkParentChildIntegrity(ss *SqlSupplier, config relationalCheckConfig) store.IntegrityCheckResult { + var result store.IntegrityCheckResult + var data store.RelationalIntegrityCheckData + + config.sortRecords = true + data.Records, result.Err = getOrphanedRecords(ss, config) + if result.Err != nil { + mlog.Error(result.Err.Error()) + return result + } + data.ParentName = config.parentName + data.ChildName = config.childName + data.ParentIdAttr = config.parentIdAttr + data.ChildIdAttr = config.childIdAttr + result.Data = data + + return result +} + +func checkChannelsCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "CommandWebhooks", + childIdAttr: "Id", + }) +} + +func checkChannelsChannelMemberHistoryIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "ChannelMemberHistory", + childIdAttr: "", + }) +} + +func checkChannelsChannelMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "ChannelMembers", + childIdAttr: "", + }) +} + +func checkChannelsIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "IncomingWebhooks", + childIdAttr: "Id", + }) +} + +func checkChannelsOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "OutgoingWebhooks", + childIdAttr: "Id", + }) +} + +func checkChannelsPostsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Channels", + parentIdAttr: "ChannelId", + childName: "Posts", + childIdAttr: "Id", + }) +} + +func checkCommandsCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Commands", + parentIdAttr: "CommandId", + childName: "CommandWebhooks", + childIdAttr: "Id", + }) +} + +func checkPostsFileInfoIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Posts", + parentIdAttr: "PostId", + childName: "FileInfo", + childIdAttr: "", + }) +} + +func checkPostsPostsParentIdIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Posts", + parentIdAttr: "ParentId", + childName: "Posts", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkPostsPostsRootIdIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Posts", + parentIdAttr: "RootId", + childName: "Posts", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkPostsReactionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Posts", + parentIdAttr: "PostId", + childName: "Reactions", + childIdAttr: "", + }) +} + +func checkSchemesChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Schemes", + parentIdAttr: "SchemeId", + childName: "Channels", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkSchemesTeamsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Schemes", + parentIdAttr: "SchemeId", + childName: "Teams", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkSessionsAuditsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Sessions", + parentIdAttr: "SessionId", + childName: "Audits", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkTeamsChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Teams", + parentIdAttr: "TeamId", + childName: "Channels", + childIdAttr: "Id", + }) +} + +func checkTeamsCommandsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Teams", + parentIdAttr: "TeamId", + childName: "Commands", + childIdAttr: "Id", + }) +} + +func checkTeamsIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Teams", + parentIdAttr: "TeamId", + childName: "IncomingWebhooks", + childIdAttr: "Id", + }) +} + +func checkTeamsOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Teams", + parentIdAttr: "TeamId", + childName: "OutgoingWebhooks", + childIdAttr: "Id", + }) +} + +func checkTeamsTeamMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Teams", + parentIdAttr: "TeamId", + childName: "TeamMembers", + childIdAttr: "", + }) +} + +func checkUsersAuditsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Audits", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkUsersCommandWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "CommandWebhooks", + childIdAttr: "Id", + }) +} + +func checkUsersChannelMemberHistoryIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "ChannelMemberHistory", + childIdAttr: "", + }) +} + +func checkUsersChannelMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "ChannelMembers", + childIdAttr: "", + }) +} + +func checkUsersChannelsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "CreatorId", + childName: "Channels", + childIdAttr: "Id", + canParentIdBeEmpty: true, + }) +} + +func checkUsersCommandsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "CreatorId", + childName: "Commands", + childIdAttr: "Id", + }) +} + +func checkUsersCompliancesIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Compliances", + childIdAttr: "Id", + }) +} + +func checkUsersEmojiIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "CreatorId", + childName: "Emoji", + childIdAttr: "Id", + }) +} + +func checkUsersFileInfoIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Posts", + parentIdAttr: "CreatorId", + childName: "FileInfo", + childIdAttr: "", + }) +} + +func checkUsersIncomingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "IncomingWebhooks", + childIdAttr: "Id", + }) +} + +func checkUsersOAuthAccessDataIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "OAuthAccessData", + childIdAttr: "Token", + }) +} + +func checkUsersOAuthAppsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "CreatorId", + childName: "OAuthApps", + childIdAttr: "Id", + }) +} + +func checkUsersOAuthAuthDataIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "OAuthAuthData", + childIdAttr: "Code", + }) +} + +func checkUsersOutgoingWebhooksIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "CreatorId", + childName: "OutgoingWebhooks", + childIdAttr: "Id", + }) +} + +func checkUsersPostsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Posts", + childIdAttr: "Id", + }) +} + +func checkUsersPreferencesIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Preferences", + childIdAttr: "", + }) +} + +func checkUsersReactionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Reactions", + childIdAttr: "", + }) +} + +func checkUsersSessionsIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Sessions", + childIdAttr: "Id", + }) +} + +func checkUsersStatusIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "Status", + childIdAttr: "", + }) +} + +func checkUsersTeamMembersIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "TeamMembers", + childIdAttr: "", + }) +} + +func checkUsersUserAccessTokensIntegrity(ss *SqlSupplier) store.IntegrityCheckResult { + return checkParentChildIntegrity(ss, relationalCheckConfig{ + parentName: "Users", + parentIdAttr: "UserId", + childName: "UserAccessTokens", + childIdAttr: "Id", + }) +} + +func checkChannelsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkChannelsCommandWebhooksIntegrity(ss) + results <- checkChannelsChannelMemberHistoryIntegrity(ss) + results <- checkChannelsChannelMembersIntegrity(ss) + results <- checkChannelsIncomingWebhooksIntegrity(ss) + results <- checkChannelsOutgoingWebhooksIntegrity(ss) + results <- checkChannelsPostsIntegrity(ss) +} + +func checkCommandsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkCommandsCommandWebhooksIntegrity(ss) +} + +func checkPostsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkPostsFileInfoIntegrity(ss) + results <- checkPostsPostsParentIdIntegrity(ss) + results <- checkPostsPostsRootIdIntegrity(ss) + results <- checkPostsReactionsIntegrity(ss) +} + +func checkSchemesIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkSchemesChannelsIntegrity(ss) + results <- checkSchemesTeamsIntegrity(ss) +} + +func checkSessionsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkSessionsAuditsIntegrity(ss) +} + +func checkTeamsIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkTeamsChannelsIntegrity(ss) + results <- checkTeamsCommandsIntegrity(ss) + results <- checkTeamsIncomingWebhooksIntegrity(ss) + results <- checkTeamsOutgoingWebhooksIntegrity(ss) + results <- checkTeamsTeamMembersIntegrity(ss) +} + +func checkUsersIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + results <- checkUsersAuditsIntegrity(ss) + results <- checkUsersCommandWebhooksIntegrity(ss) + results <- checkUsersChannelMemberHistoryIntegrity(ss) + results <- checkUsersChannelMembersIntegrity(ss) + results <- checkUsersChannelsIntegrity(ss) + results <- checkUsersCommandsIntegrity(ss) + results <- checkUsersCompliancesIntegrity(ss) + results <- checkUsersEmojiIntegrity(ss) + results <- checkUsersFileInfoIntegrity(ss) + results <- checkUsersIncomingWebhooksIntegrity(ss) + results <- checkUsersOAuthAccessDataIntegrity(ss) + results <- checkUsersOAuthAppsIntegrity(ss) + results <- checkUsersOAuthAuthDataIntegrity(ss) + results <- checkUsersOutgoingWebhooksIntegrity(ss) + results <- checkUsersPostsIntegrity(ss) + results <- checkUsersPreferencesIntegrity(ss) + results <- checkUsersReactionsIntegrity(ss) + results <- checkUsersSessionsIntegrity(ss) + results <- checkUsersStatusIntegrity(ss) + results <- checkUsersTeamMembersIntegrity(ss) + results <- checkUsersUserAccessTokensIntegrity(ss) +} + +func CheckRelationalIntegrity(ss *SqlSupplier, results chan<- store.IntegrityCheckResult) { + mlog.Info("Starting relational integrity checks...") + checkChannelsIntegrity(ss, results) + checkCommandsIntegrity(ss, results) + checkPostsIntegrity(ss, results) + checkSchemesIntegrity(ss, results) + checkSessionsIntegrity(ss, results) + checkTeamsIntegrity(ss, results) + checkUsersIntegrity(ss, results) + mlog.Info("Done with relational integrity checks") + close(results) +} diff --git a/store/sqlstore/integrity_test.go b/store/sqlstore/integrity_test.go new file mode 100644 index 0000000000..02125a4cee --- /dev/null +++ b/store/sqlstore/integrity_test.go @@ -0,0 +1,1539 @@ +// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package sqlstore + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" +) + +func createAudit(ss store.Store, userId, sessionId string) *model.Audit { + audit := model.Audit{ + UserId: userId, + SessionId: sessionId, + IpAddress: "ipaddress", + Action: "Action", + } + ss.Audit().Save(&audit) + return &audit +} + +func createChannel(ss store.Store, teamId, creatorId string) *model.Channel { + m := model.Channel{} + m.TeamId = teamId + m.CreatorId = creatorId + m.DisplayName = "Name" + m.Name = "zz" + model.NewId() + "b" + m.Type = model.CHANNEL_OPEN + c, _ := ss.Channel().Save(&m, -1) + return c +} + +func createChannelWithSchemeId(ss store.Store, schemeId *string) *model.Channel { + m := model.Channel{} + m.SchemeId = schemeId + m.TeamId = model.NewId() + m.CreatorId = model.NewId() + m.DisplayName = "Name" + m.Name = "zz" + model.NewId() + "b" + m.Type = model.CHANNEL_OPEN + c, _ := ss.Channel().Save(&m, -1) + return c +} + +func createCommand(ss store.Store, userId, teamId string) * model.Command { + m := model.Command{} + m.CreatorId = userId + m.Method = model.COMMAND_METHOD_POST + m.TeamId = teamId + m.URL = "http://nowhere.com/" + m.Trigger = "trigger" + cmd, _ := ss.Command().Save(&m) + return cmd +} + +func createChannelMember(ss store.Store, channelId, userId string) *model.ChannelMember { + m := model.ChannelMember{} + m.ChannelId = channelId + m.UserId = userId + m.NotifyProps = model.GetDefaultChannelNotifyProps() + cm, _ := ss.Channel().SaveMember(&m) + return cm +} + +func createChannelMemberHistory(ss store.Store, channelId, userId string) *model.ChannelMemberHistory { + m := model.ChannelMemberHistory{} + m.ChannelId = channelId + m.UserId = userId + ss.ChannelMemberHistory().LogJoinEvent(userId, channelId, model.GetMillis()) + return &m +} + +func createChannelWithTeamId(ss store.Store, id string) *model.Channel { + return createChannel(ss, id, model.NewId()); +} + +func createChannelWithCreatorId(ss store.Store, id string) *model.Channel { + return createChannel(ss, model.NewId(), id); +} + +func createChannelMemberWithChannelId(ss store.Store, id string) *model.ChannelMember { + return createChannelMember(ss, id, model.NewId()); +} + +func createChannelMemberWithUserId(ss store.Store, id string) *model.ChannelMember { + return createChannelMember(ss, model.NewId(), id); +} + +func createCommandWebhook(ss store.Store, commandId, userId, channelId string) *model.CommandWebhook { + m := model.CommandWebhook{} + m.CommandId = commandId + m.UserId = userId + m.ChannelId = channelId + cwh, _ := ss.CommandWebhook().Save(&m) + return cwh +} + +func createCompliance(ss store.Store, userId string) *model.Compliance { + m := model.Compliance{} + m.UserId = userId + m.Desc = "Audit" + m.Status = model.COMPLIANCE_STATUS_FAILED + m.StartAt = model.GetMillis() - 1 + m.EndAt = model.GetMillis() + 1 + m.Type = model.COMPLIANCE_TYPE_ADHOC + c, _ := ss.Compliance().Save(&m) + return c +} + +func createEmoji(ss store.Store, userId string) *model.Emoji { + m := model.Emoji{} + m.CreatorId = userId + m.Name = "emoji" + emoji, _ := ss.Emoji().Save(&m) + return emoji +} + +func createFileInfo(ss store.Store, postId, userId string) *model.FileInfo { + m := model.FileInfo{} + m.PostId = postId + m.CreatorId = userId + m.Path = "some/path/to/file" + info, _ := ss.FileInfo().Save(&m) + return info +} + +func createIncomingWebhook(ss store.Store, userId, channelId, teamId string) *model.IncomingWebhook { + m := model.IncomingWebhook{} + m.UserId = userId + m.ChannelId = channelId + m.TeamId = teamId + wh, _ := ss.Webhook().SaveIncoming(&m) + return wh +} + +func createOAuthAccessData(ss store.Store, userId string) *model.AccessData { + m := model.AccessData{} + m.ClientId = model.NewId() + m.UserId = userId + m.Token = model.NewId() + m.RefreshToken = model.NewId() + m.RedirectUri = "http://example.com" + ad, _ := ss.OAuth().SaveAccessData(&m) + return ad +} + +func createOAuthApp(ss store.Store, userId string) *model.OAuthApp { + m := model.OAuthApp{} + m.CreatorId = userId + m.CallbackUrls = []string{"https://nowhere.com"} + m.Homepage = "https://nowhere.com" + m.Id = "" + m.Name = "TestApp" + model.NewId() + app, _ := ss.OAuth().SaveApp(&m) + return app +} + +func createOAuthAuthData(ss store.Store, userId string) *model.AuthData { + m := model.AuthData{} + m.ClientId = model.NewId() + m.UserId = userId + m.Code = model.NewId() + m.RedirectUri = "http://example.com" + ad, _ := ss.OAuth().SaveAuthData(&m) + return ad +} + +func createOutgoingWebhook(ss store.Store, userId, channelId, teamId string) *model.OutgoingWebhook { + m := model.OutgoingWebhook{} + m.CreatorId = userId + m.ChannelId = channelId + m.TeamId = teamId + m.Token = model.NewId() + m.CallbackURLs = []string{"http://nowhere.com/"} + wh, _ := ss.Webhook().SaveOutgoing(&m) + return wh +} + +func createPost(ss store.Store, channelId, userId, rootId, parentId string) *model.Post { + m := model.Post{} + m.ChannelId = channelId + m.UserId = userId + m.RootId = rootId + m.ParentId = parentId + m.Message = "zz" + model.NewId() + "b" + p, _ := ss.Post().Save(&m) + return p +} + +func createPostWithChannelId(ss store.Store, id string) *model.Post { + return createPost(ss, id, model.NewId(), "", ""); +} + +func createPostWithUserId(ss store.Store, id string) *model.Post { + return createPost(ss, model.NewId(), id, "", ""); +} + +func createPreferences(ss store.Store, userId string) *model.Preferences { + preferences := model.Preferences{ + { + UserId: userId, + Name: model.NewId(), + Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW, + Value: "somevalue", + }, + } + ss.Preference().Save(&preferences) + return &preferences +} + +func createReaction(ss store.Store, userId, postId string) *model.Reaction { + reaction := &model.Reaction{ + UserId: userId, + PostId: postId, + EmojiName: model.NewId(), + } + reaction, _ = ss.Reaction().Save(reaction) + return reaction +} + +func createDefaultRoles(ss store.Store) { + ss.Role().Save(&model.Role{ + Name: model.TEAM_ADMIN_ROLE_ID, + DisplayName: model.TEAM_ADMIN_ROLE_ID, + Permissions: []string{ + model.PERMISSION_DELETE_OTHERS_POSTS.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.TEAM_USER_ROLE_ID, + DisplayName: model.TEAM_USER_ROLE_ID, + Permissions: []string{ + model.PERMISSION_VIEW_TEAM.Id, + model.PERMISSION_ADD_USER_TO_TEAM.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.TEAM_GUEST_ROLE_ID, + DisplayName: model.TEAM_GUEST_ROLE_ID, + Permissions: []string{ + model.PERMISSION_VIEW_TEAM.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.CHANNEL_ADMIN_ROLE_ID, + DisplayName: model.CHANNEL_ADMIN_ROLE_ID, + Permissions: []string{ + model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS.Id, + model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.CHANNEL_USER_ROLE_ID, + DisplayName: model.CHANNEL_USER_ROLE_ID, + Permissions: []string{ + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_CREATE_POST.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.CHANNEL_GUEST_ROLE_ID, + DisplayName: model.CHANNEL_GUEST_ROLE_ID, + Permissions: []string{ + model.PERMISSION_READ_CHANNEL.Id, + model.PERMISSION_CREATE_POST.Id, + }, + }) +} + +func createScheme(ss store.Store) *model.Scheme { + m := model.Scheme{} + m.DisplayName = model.NewId() + m.Name = model.NewId() + m.Description = model.NewId() + m.Scope = model.SCHEME_SCOPE_CHANNEL + s, _ := ss.Scheme().Save(&m) + return s +} + +func createSession(ss store.Store, userId string) *model.Session { + m := model.Session{} + m.UserId = userId + s, _ := ss.Session().Save(&m) + return s +} + +func createStatus(ss store.Store, userId string) *model.Status { + m := model.Status{} + m.UserId = userId + m.Status = model.STATUS_ONLINE + ss.Status().SaveOrUpdate(&m) + return &m +} + +func createTeam(ss store.Store, userId string) *model.Team { + m := model.Team{} + m.DisplayName = "DisplayName" + m.Type = model.TEAM_OPEN + m.Email = "test@example.com" + m.Name = "z-z-z" + model.NewId() + "b" + t, _ := ss.Team().Save(&m) + return t +} + +func createTeamMember(ss store.Store, teamId, userId string) *model.TeamMember { + m := model.TeamMember{} + m.TeamId = teamId + m.UserId = userId + tm, _ := ss.Team().SaveMember(&m, -1) + return tm +} + +func createTeamWithSchemeId(ss store.Store, schemeId *string) *model.Team { + m := model.Team{} + m.SchemeId = schemeId + m.DisplayName = "DisplayName" + m.Type = model.TEAM_OPEN + m.Email = "test@example.com" + m.Name = "z-z-z" + model.NewId() + "b" + t, _ := ss.Team().Save(&m) + return t +} + +func createUser(ss store.Store) *model.User { + m := model.User{} + m.Username = model.NewId() + m.Email = "test@example.com" + user, _ := ss.User().Save(&m) + return user +} + +func createUserAccessToken(ss store.Store, userId string) *model.UserAccessToken { + m := model.UserAccessToken{} + m.UserId = userId + m.Token = model.NewId() + uat, _ := ss.UserAccessToken().Save(&m) + return uat +} + +func TestCheckIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + ss.DropAllTables() + t.Run("generate reports with no records", func(t *testing.T) { + results := ss.CheckIntegrity() + require.NotNil(t, results) + for result := range results { + require.IsType(t, store.IntegrityCheckResult{}, result) + require.Nil(t, result.Err) + switch data := result.Data.(type) { + case store.RelationalIntegrityCheckData: + require.Len(t, data.Records, 0) + } + } + }) + }) +} + +func TestCheckParentChildIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + t.Run("should receive an error", func(t *testing.T) { + config := relationalCheckConfig{ + parentName: "NotValid", + parentIdAttr: "NotValid", + childName: "NotValid", + childIdAttr: "NotValid", + } + result := checkParentChildIntegrity(supplier, config) + require.NotNil(t, result.Err) + require.Empty(t, result.Data) + }) + }) +} + +func TestCheckChannelsCommandWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channelId := model.NewId() + cwh := createCommandWebhook(ss, model.NewId(), model.NewId(), channelId) + result := checkChannelsCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: channelId, + ChildId: cwh.Id, + }, data.Records[0]) + dbmap.Delete(cwh) + }) + }) +} + +func TestCheckChannelsChannelMemberHistoryIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsChannelMemberHistoryIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channel := createChannel(ss, model.NewId(), model.NewId()) + user := createUser(ss) + cmh := createChannelMemberHistory(ss, channel.Id, user.Id) + dbmap.Delete(channel) + result := checkChannelsChannelMemberHistoryIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: cmh.ChannelId, + }, data.Records[0]) + dbmap.Delete(user) + dbmap.Exec(`DELETE FROM ChannelMemberHistory`) + }) + }) +} + +func TestCheckChannelsChannelMembersIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsChannelMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channel := createChannel(ss, model.NewId(), model.NewId()) + member := createChannelMemberWithChannelId(ss, channel.Id) + dbmap.Delete(channel) + result := checkChannelsChannelMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: member.ChannelId, + }, data.Records[0]) + ss.Channel().PermanentDeleteMembersByChannel(member.ChannelId) + }) + }) +} + +func TestCheckChannelsIncomingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channelId := model.NewId() + wh := createIncomingWebhook(ss, model.NewId(), channelId, model.NewId()) + result := checkChannelsIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: channelId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckChannelsOutgoingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channel := createChannel(ss, model.NewId(), model.NewId()) + channelId := channel.Id + wh := createOutgoingWebhook(ss, model.NewId(), channelId, model.NewId()) + dbmap.Delete(channel) + result := checkChannelsOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: channelId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckChannelsPostsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkChannelsPostsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + post := createPostWithChannelId(ss, model.NewId()) + result := checkChannelsPostsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: post.ChannelId, + ChildId: post.Id, + }, data.Records[0]) + dbmap.Delete(post) + }) + }) +} + +func TestCheckCommandsCommandWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkCommandsCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + commandId := model.NewId() + cwh := createCommandWebhook(ss, commandId, model.NewId(), model.NewId()) + result := checkCommandsCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: commandId, + ChildId: cwh.Id, + }, data.Records[0]) + dbmap.Delete(cwh) + }) + }) +} + +func TestCheckPostsFileInfoIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkPostsFileInfoIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + postId := model.NewId() + info := createFileInfo(ss, postId, model.NewId()) + result := checkPostsFileInfoIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: postId, + }, data.Records[0]) + dbmap.Delete(info) + }) + }) +} + +func TestCheckPostsPostsParentIdIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkPostsPostsParentIdIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + root := createPost(ss, model.NewId(), model.NewId(), "", "") + parent := createPost(ss, model.NewId(), model.NewId(), root.Id, root.Id) + parentId := parent.Id + post := createPost(ss, model.NewId(), model.NewId(), root.Id, parent.Id) + dbmap.Delete(parent) + result := checkPostsPostsParentIdIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: parentId, + ChildId: post.Id, + }, data.Records[0]) + dbmap.Delete(root) + dbmap.Delete(post) + }) + }) +} + +func TestCheckPostsPostsRootIdIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkPostsPostsRootIdIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + root := createPost(ss, model.NewId(), model.NewId(), "", "") + rootId := root.Id + post := createPost(ss, model.NewId(), model.NewId(), root.Id, root.Id) + dbmap.Delete(root) + result := checkPostsPostsRootIdIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: rootId, + ChildId: post.Id, + }, data.Records[0]) + dbmap.Delete(post) + }) + }) +} + +func TestCheckPostsReactionsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkPostsReactionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + postId := model.NewId() + reaction := createReaction(ss, model.NewId(), postId) + result := checkPostsReactionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: postId, + }, data.Records[0]) + dbmap.Delete(reaction) + }) + }) +} + +func TestCheckSchemesChannelsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkSchemesChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + createDefaultRoles(ss) + scheme := createScheme(ss) + schemeId := scheme.Id + channel := createChannelWithSchemeId(ss, &schemeId) + dbmap.Delete(scheme) + result := checkSchemesChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: schemeId, + ChildId: channel.Id, + }, data.Records[0]) + dbmap.Delete(channel) + }) + }) +} + +func TestCheckSchemesTeamsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkSchemesTeamsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + createDefaultRoles(ss) + scheme := createScheme(ss) + schemeId := scheme.Id + team := createTeamWithSchemeId(ss, &schemeId) + dbmap.Delete(scheme) + result := checkSchemesTeamsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: schemeId, + ChildId: team.Id, + }, data.Records[0]) + dbmap.Delete(team) + }) + }) +} + +func TestCheckSessionsAuditsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkSessionsAuditsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + session := createSession(ss, model.NewId()) + sessionId := session.Id + audit := createAudit(ss, userId, sessionId) + dbmap.Delete(session) + result := checkSessionsAuditsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: sessionId, + ChildId: audit.Id, + }, data.Records[0]) + ss.Audit().PermanentDeleteByUser(userId) + }) + }) +} + +func TestCheckTeamsChannelsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkTeamsChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channel := createChannelWithTeamId(ss, model.NewId()) + result := checkTeamsChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: channel.TeamId, + ChildId: channel.Id, + }, data.Records[0]) + dbmap.Delete(channel) + }) + }) +} + +func TestCheckTeamsCommandsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkTeamsCommandsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + teamId := model.NewId() + cmd := createCommand(ss, model.NewId(), teamId) + result := checkTeamsCommandsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: teamId, + ChildId: cmd.Id, + }, data.Records[0]) + dbmap.Delete(cmd) + }) + }) +} + +func TestCheckTeamsIncomingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkTeamsIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + teamId := model.NewId() + wh := createIncomingWebhook(ss, model.NewId(), model.NewId(), teamId) + result := checkTeamsIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: teamId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckTeamsOutgoingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkTeamsOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + teamId := model.NewId() + wh := createOutgoingWebhook(ss, model.NewId(), model.NewId(), teamId) + result := checkTeamsOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: teamId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckTeamsTeamMembersIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkTeamsTeamMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + team := createTeam(ss, model.NewId()) + member := createTeamMember(ss, team.Id, model.NewId()) + dbmap.Delete(team) + result := checkTeamsTeamMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: team.Id, + }, data.Records[0]) + ss.Team().RemoveAllMembersByTeam(member.TeamId) + }) + }) +} + +func TestCheckUsersAuditsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersAuditsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + audit := createAudit(ss, userId, model.NewId()) + dbmap.Delete(user) + result := checkUsersAuditsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: audit.Id, + }, data.Records[0]) + ss.Audit().PermanentDeleteByUser(userId) + }) + }) +} + +func TestCheckUsersCommandWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + cwh := createCommandWebhook(ss, model.NewId(), userId, model.NewId()) + result := checkUsersCommandWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: cwh.Id, + }, data.Records[0]) + dbmap.Delete(cwh) + }) + }) +} + +func TestCheckUsersChannelsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + channel := createChannelWithCreatorId(ss, model.NewId()) + result := checkUsersChannelsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: channel.CreatorId, + ChildId: channel.Id, + }, data.Records[0]) + dbmap.Delete(channel) + }) + }) +} + +func TestCheckUsersChannelMemberHistoryIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersChannelMemberHistoryIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + channel := createChannel(ss, model.NewId(), model.NewId()) + cmh := createChannelMemberHistory(ss, channel.Id, user.Id) + dbmap.Delete(user) + result := checkUsersChannelMemberHistoryIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: cmh.UserId, + }, data.Records[0]) + dbmap.Delete(channel) + dbmap.Exec(`DELETE FROM ChannelMemberHistory`) + }) + }) +} + +func TestCheckUsersChannelMembersIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersChannelMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + channel := createChannelWithCreatorId(ss, user.Id) + member := createChannelMember(ss, channel.Id, user.Id) + dbmap.Delete(user) + result := checkUsersChannelMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: member.UserId, + }, data.Records[0]) + dbmap.Delete(channel) + ss.Channel().PermanentDeleteMembersByUser(member.UserId) + }) + }) +} + +func TestCheckUsersCommandsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersCommandsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + cmd := createCommand(ss, userId, model.NewId()) + result := checkUsersCommandsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: cmd.Id, + }, data.Records[0]) + dbmap.Delete(cmd) + }) + }) +} + +func TestCheckUsersCompliancesIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersCompliancesIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + compliance := createCompliance(ss, userId) + dbmap.Delete(user) + result := checkUsersCompliancesIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: compliance.Id, + }, data.Records[0]) + dbmap.Delete(compliance) + }) + }) +} + +func TestCheckUsersEmojiIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersEmojiIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + emoji := createEmoji(ss, userId) + dbmap.Delete(user) + result := checkUsersEmojiIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: emoji.Id, + }, data.Records[0]) + dbmap.Delete(emoji) + }) + }) +} + +func TestCheckUsersFileInfoIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersFileInfoIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + info := createFileInfo(ss, model.NewId(), userId) + dbmap.Delete(user) + result := checkUsersFileInfoIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + }, data.Records[0]) + dbmap.Delete(info) + }) + }) +} + +func TestCheckUsersIncomingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + wh := createIncomingWebhook(ss, userId, model.NewId(), model.NewId()) + result := checkUsersIncomingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckUsersOAuthAccessDataIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersOAuthAccessDataIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + ad := createOAuthAccessData(ss, userId) + dbmap.Delete(user) + result := checkUsersOAuthAccessDataIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: ad.Token, + }, data.Records[0]) + ss.OAuth().RemoveAccessData(ad.Token) + }) + }) +} + +func TestCheckUsersOAuthAppsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersOAuthAppsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + app := createOAuthApp(ss, userId) + dbmap.Delete(user) + result := checkUsersOAuthAppsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: app.Id, + }, data.Records[0]) + ss.OAuth().DeleteApp(app.Id) + }) + }) +} + +func TestCheckUsersOAuthAuthDataIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersOAuthAuthDataIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + ad := createOAuthAuthData(ss, userId) + dbmap.Delete(user) + result := checkUsersOAuthAuthDataIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: ad.Code, + }, data.Records[0]) + ss.OAuth().RemoveAuthData(ad.Code) + }) + }) +} + +func TestCheckUsersOutgoingWebhooksIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + wh := createOutgoingWebhook(ss, userId, model.NewId(), model.NewId()) + result := checkUsersOutgoingWebhooksIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: wh.Id, + }, data.Records[0]) + dbmap.Delete(wh) + }) + }) +} + +func TestCheckUsersPostsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersPostsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + post := createPostWithUserId(ss, model.NewId()) + result := checkUsersPostsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: post.UserId, + ChildId: post.Id, + }, data.Records[0]) + dbmap.Delete(post) + }) + }) +} + +func TestCheckUsersPreferencesIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersPreferencesIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + preferences := createPreferences(ss, userId) + dbmap.Delete(user) + result := checkUsersPreferencesIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + }, data.Records[0]) + dbmap.Delete(preferences) + }) + }) +} + +func TestCheckUsersReactionsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersReactionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + reaction := createReaction(ss, user.Id, model.NewId()) + dbmap.Delete(user) + result := checkUsersReactionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + }, data.Records[0]) + dbmap.Delete(reaction) + }) + }) +} + +func TestCheckUsersSessionsIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersSessionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + userId := model.NewId() + session := createSession(ss, userId) + result := checkUsersSessionsIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: session.Id, + }, data.Records[0]) + dbmap.Delete(session) + }) + }) +} + +func TestCheckUsersStatusIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersStatusIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + status := createStatus(ss, user.Id) + dbmap.Delete(user) + result := checkUsersStatusIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + }, data.Records[0]) + dbmap.Delete(status) + }) + }) +} + +func TestCheckUsersTeamMembersIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersTeamMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + team := createTeam(ss, user.Id) + member := createTeamMember(ss, team.Id, user.Id) + dbmap.Delete(user) + result := checkUsersTeamMembersIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: member.UserId, + }, data.Records[0]) + ss.Team().RemoveAllMembersByTeam(member.TeamId) + dbmap.Delete(team) + }) + }) +} + +func TestCheckUsersUserAccessTokensIntegrity(t *testing.T) { + StoreTest(t, func(t *testing.T, ss store.Store) { + supplier := ss.(*store.LayeredStore).DatabaseLayer.(*SqlSupplier) + dbmap := supplier.GetMaster() + + t.Run("should generate a report with no records", func(t *testing.T) { + result := checkUsersUserAccessTokensIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 0) + }) + + t.Run("should generate a report with one record", func(t *testing.T) { + user := createUser(ss) + userId := user.Id + uat := createUserAccessToken(ss, user.Id) + dbmap.Delete(user) + result := checkUsersUserAccessTokensIntegrity(supplier) + require.Nil(t, result.Err) + data := result.Data.(store.RelationalIntegrityCheckData) + require.Len(t, data.Records, 1) + require.Equal(t, store.OrphanedRecord{ + ParentId: userId, + ChildId: uat.Id, + }, data.Records[0]) + ss.UserAccessToken().Delete(uat.Id) + }) + }) +} diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 675b0029c9..1a3de5eb3d 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -1066,6 +1066,12 @@ func (ss *SqlSupplier) getQueryBuilder() sq.StatementBuilderType { return builder } +func (ss *SqlSupplier) CheckIntegrity() <-chan store.IntegrityCheckResult { + results := make(chan store.IntegrityCheckResult) + go CheckRelationalIntegrity(ss, results) + return results +} + type mattermConverter struct{} func (me mattermConverter) ToDb(val interface{}) (interface{}, error) { diff --git a/store/store.go b/store/store.go index 158c98d929..76f87fb0b0 100644 --- a/store/store.go +++ b/store/store.go @@ -55,6 +55,7 @@ type Store interface { TotalMasterDbConnections() int TotalReadDbConnections() int TotalSearchDbConnections() int + CheckIntegrity() <-chan IntegrityCheckResult } type TeamStore interface { @@ -627,3 +628,21 @@ type UserGetByIdsOpts struct { // Since filters the users based on their UpdateAt timestamp. Since int64 } + +type OrphanedRecord struct { + ParentId string + ChildId string +} + +type RelationalIntegrityCheckData struct { + ParentName string + ChildName string + ParentIdAttr string + ChildIdAttr string + Records []OrphanedRecord +} + +type IntegrityCheckResult struct { + Data interface{} + Err error +} diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go index 360a5cf0c8..c5d7691607 100644 --- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go +++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go @@ -78,6 +78,22 @@ func (_m *LayeredStoreDatabaseLayer) ChannelMemberHistory() store.ChannelMemberH return r0 } +// CheckIntegrity provides a mock function with given fields: +func (_m *LayeredStoreDatabaseLayer) CheckIntegrity() <-chan store.IntegrityCheckResult { + ret := _m.Called() + + var r0 <-chan store.IntegrityCheckResult + if rf, ok := ret.Get(0).(func() <-chan store.IntegrityCheckResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan store.IntegrityCheckResult) + } + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *LayeredStoreDatabaseLayer) Close() { _m.Called() diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go index 63ca8cde83..1fa222c35f 100644 --- a/store/storetest/mocks/Store.go +++ b/store/storetest/mocks/Store.go @@ -76,6 +76,22 @@ func (_m *Store) ChannelMemberHistory() store.ChannelMemberHistoryStore { return r0 } +// CheckIntegrity provides a mock function with given fields: +func (_m *Store) CheckIntegrity() <-chan store.IntegrityCheckResult { + ret := _m.Called() + + var r0 <-chan store.IntegrityCheckResult + if rf, ok := ret.Get(0).(func() <-chan store.IntegrityCheckResult); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan store.IntegrityCheckResult) + } + } + + return r0 +} + // Close provides a mock function with given fields: func (_m *Store) Close() { _m.Called() diff --git a/store/storetest/store.go b/store/storetest/store.go index fed9b6cede..1d09adf8f8 100644 --- a/store/storetest/store.go +++ b/store/storetest/store.go @@ -87,6 +87,9 @@ func (s *Store) TotalMasterDbConnections() int { return 1 } func (s *Store) TotalReadDbConnections() int { return 1 } func (s *Store) TotalSearchDbConnections() int { return 1 } func (s *Store) GetCurrentSchemaVersion() string { return "" } +func (s *Store) CheckIntegrity() <-chan store.IntegrityCheckResult { + return make(chan store.IntegrityCheckResult) +} func (s *Store) AssertExpectations(t mock.TestingT) bool { return mock.AssertExpectationsForObjects(t, From 614ca90ca2d42a30477943b855c1a919362be666 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Fri, 30 Aug 2019 22:00:28 +0900 Subject: [PATCH 10/47] [MM-18058] Use GetAnyUnreadPostCountForChannel for NotifyProps.push == all (#11979) * Use GetAnyUnreadPostCountForChannel for NotifyProps.push == all * Extract out msg building and add unit test --- app/helper_test.go | 20 +++++++ app/notification_push.go | 104 ++++++++++++++++++++-------------- app/notification_push_test.go | 51 +++++++++++++++++ 3 files changed, 131 insertions(+), 44 deletions(-) diff --git a/app/helper_test.go b/app/helper_test.go index adfc0fbfe9..947f07888c 100644 --- a/app/helper_test.go +++ b/app/helper_test.go @@ -296,6 +296,26 @@ func (me *TestHelper) CreatePost(channel *model.Channel) *model.Post { return post } +func (me *TestHelper) CreateMessagePost(channel *model.Channel, message string) *model.Post { + post := &model.Post{ + UserId: me.BasicUser.Id, + ChannelId: channel.Id, + Message: message, + CreateAt: model.GetMillis() - 10000, + } + + utils.DisableDebugLogForTest() + var err *model.AppError + if post, err = me.App.CreatePost(post, channel, false); err != nil { + mlog.Error(err.Error()) + + time.Sleep(time.Second) + panic(err) + } + utils.EnableDebugLogForTest() + return post +} + func (me *TestHelper) LinkUserToTeam(user *model.User, team *model.Team) { utils.DisableDebugLogForTest() diff --git a/app/notification_push.go b/app/notification_push.go index edcd7310c3..aea201ce6b 100644 --- a/app/notification_push.go +++ b/app/notification_push.go @@ -53,58 +53,16 @@ func (hub *PushNotificationsHub) GetGoChannelFromUserId(userId string) chan Push } func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, - explicitMention, channelWideMention bool, replyToThreadType string) *model.AppError { - cfg := a.Config() + explicitMention bool, channelWideMention bool, replyToThreadType string) *model.AppError { sessions, err := a.getMobileAppSessions(user.Id) if err != nil { return err } - msg := model.PushNotification{ - Category: model.CATEGORY_CAN_REPLY, - Version: model.PUSH_MESSAGE_V2, - Type: model.PUSH_TYPE_MESSAGE, - TeamId: channel.TeamId, - ChannelId: channel.Id, - PostId: post.Id, - RootId: post.RootId, - SenderId: post.UserId, - } - - if unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id); err != nil { - msg.Badge = 1 - mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id)) - } else { - msg.Badge = int(unreadCount) - } - - contentsConfig := *cfg.EmailSettings.PushNotificationContents - if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { - msg.ChannelName = channelName - } - - msg.SenderName = senderName - if ou, ok := post.Props["override_username"].(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride { - msg.OverrideUsername = ou - msg.SenderName = ou - } - - if oi, ok := post.Props["override_icon_url"].(string); ok && *cfg.ServiceSettings.EnablePostIconOverride { - msg.OverrideIconUrl = oi - } - - if fw, ok := post.Props["from_webhook"].(string); ok { - msg.FromWebhook = fw - } - - userLocale := utils.GetUserTranslations(user.Locale) - hasFiles := post.FileIds != nil && len(post.FileIds) > 0 - - msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale) + msg := a.BuildPushNotificationMessage(post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType) for _, session := range sessions { - if session.IsExpired() { continue } @@ -475,3 +433,61 @@ func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *mo return false } + +func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, + explicitMention bool, channelWideMention bool, replyToThreadType string) model.PushNotification { + + msg := model.PushNotification{ + Category: model.CATEGORY_CAN_REPLY, + Version: model.PUSH_MESSAGE_V2, + Type: model.PUSH_TYPE_MESSAGE, + TeamId: channel.TeamId, + ChannelId: channel.Id, + PostId: post.Id, + RootId: post.RootId, + SenderId: post.UserId, + } + + if user.NotifyProps["push"] == "all" { + if unreadCount, err := a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id); err != nil { + msg.Badge = 1 + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id)) + } else { + msg.Badge = int(unreadCount) + } + } else { + if unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id); err != nil { + msg.Badge = 1 + mlog.Error(fmt.Sprint("We could not get the unread message count for the user", user.Id, err), mlog.String("user_id", user.Id)) + } else { + msg.Badge = int(unreadCount) + } + } + + cfg := a.Config() + contentsConfig := *cfg.EmailSettings.PushNotificationContents + if contentsConfig != model.GENERIC_NO_CHANNEL_NOTIFICATION || channel.Type == model.CHANNEL_DIRECT { + msg.ChannelName = channelName + } + + msg.SenderName = senderName + if ou, ok := post.Props["override_username"].(string); ok && *cfg.ServiceSettings.EnablePostUsernameOverride { + msg.OverrideUsername = ou + msg.SenderName = ou + } + + if oi, ok := post.Props["override_icon_url"].(string); ok && *cfg.ServiceSettings.EnablePostIconOverride { + msg.OverrideIconUrl = oi + } + + if fw, ok := post.Props["from_webhook"].(string); ok { + msg.FromWebhook = fw + } + + userLocale := utils.GetUserTranslations(user.Locale) + hasFiles := post.FileIds != nil && len(post.FileIds) > 0 + + msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale) + + return msg +} diff --git a/app/notification_push_test.go b/app/notification_push_test.go index a7a7cf36d0..69fbb227bc 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -4,6 +4,7 @@ package app import ( + "fmt" "testing" "github.com/mattermost/mattermost-server/model" @@ -896,3 +897,53 @@ func TestGetPushNotificationMessage(t *testing.T) { }) } } + +func TestBuildPushNotificationMessage(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + team := th.CreateTeam() + sender := th.CreateUser() + receiver := th.CreateUser() + th.LinkUserToTeam(sender, team) + th.LinkUserToTeam(receiver, team) + channel := th.CreateChannel(team) + th.AddUserToChannel(sender, channel) + th.AddUserToChannel(receiver, channel) + + // Create three mention posts and two non-mention posts + th.CreateMessagePost(channel, "@channel Hello") + th.CreateMessagePost(channel, "@all Hello") + th.CreateMessagePost(channel, fmt.Sprintf("@%s Hello", receiver.Username)) + th.CreatePost(channel) + post := th.CreatePost(channel) + + for name, tc := range map[string]struct { + explicitMention bool + channelWideMention bool + replyToThreadType string + pushNotifyProps string + expectedBadge int + }{ + "only mentions included in badge count": { + explicitMention: false, + channelWideMention: true, + replyToThreadType: "", + pushNotifyProps: "mention", + expectedBadge: 3, + }, + "mentions and non-mentions included in badge count": { + explicitMention: false, + channelWideMention: true, + replyToThreadType: "", + pushNotifyProps: "all", + expectedBadge: 5, + }, + } { + t.Run(name, func(t *testing.T) { + receiver.NotifyProps["push"] = tc.pushNotifyProps + msg := th.App.BuildPushNotificationMessage(post, receiver, channel, channel.Name, sender.Username, tc.explicitMention, tc.channelWideMention, tc.replyToThreadType) + assert.Equal(t, tc.expectedBadge, msg.Badge) + }) + } +} From 031cf78c93cf6f4ca07a0e9cb93e264a4e18a4e1 Mon Sep 17 00:00:00 2001 From: jfrerich Date: Fri, 30 Aug 2019 16:57:51 -0500 Subject: [PATCH 11/47] =?UTF-8?q?[MM-15886]=20Don't=20send=20{"status":"OK?= =?UTF-8?q?"}=20json=20response=20if=20mobile=E2=80=A6=20(#11982)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/oauth.go | 1 - 1 file changed, 1 deletion(-) diff --git a/web/oauth.go b/web/oauth.go index 9bcc61d0ce..5e971c9f5a 100644 --- a/web/oauth.go +++ b/web/oauth.go @@ -305,7 +305,6 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } if action == model.OAUTH_ACTION_MOBILE { - ReturnStatusOK(w) return } From f76fdc99d82b36087755139125d5d46b3c72c8d9 Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Tue, 3 Sep 2019 14:56:44 +0200 Subject: [PATCH 12/47] when creating the go mod replace git.apache.org/thrift.git to github.com/apache/thrift (#12033) --- utils/test_files_compiler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/test_files_compiler.go b/utils/test_files_compiler.go index 3ff81c2674..7e4bb4a4a7 100644 --- a/utils/test_files_compiler.go +++ b/utils/test_files_compiler.go @@ -49,6 +49,7 @@ func CompileGo(t *testing.T, sourceCode, outputPath string) { goMod(t, dir, "init", "mattermost.com/test") goMod(t, dir, "edit", "-require", "github.com/mattermost/mattermost-server@v0.0.0") goMod(t, dir, "edit", "-replace", fmt.Sprintf("github.com/mattermost/mattermost-server@v0.0.0=%s", mattermostServerPath)) + goMod(t, dir, "edit", "-replace", fmt.Sprintf("git.apache.org/thrift.git=%s", "github.com/apache/thrift@v0.0.0-20180902110319-2566ecd5d999")) } out := &bytes.Buffer{} From c91bcd130d4c2e32d057c013612b8ad891ebb80b Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 3 Sep 2019 10:33:49 -0400 Subject: [PATCH 13/47] MM-18126 Respect show full name config setting in push and email notifications (#11996) * Respect show full name config setting in push and email notifications * Style fix --- app/notification.go | 13 +++++++++++++ app/notification_email.go | 7 +------ app/notification_push.go | 7 +------ app/notification_test.go | 23 +++++++++++++++++++++++ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/app/notification.go b/app/notification.go index 46d7a0db3c..375fcac4dd 100644 --- a/app/notification.go +++ b/app/notification.go @@ -786,3 +786,16 @@ func (e *ExplicitMentions) processText(text string, keywords map[string][]string } } } + +func (a *App) GetNotificationNameFormat(user *model.User) string { + if !*a.Config().PrivacySettings.ShowFullName { + return model.SHOW_USERNAME + } + + data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT) + if err != nil { + return *a.Config().TeamSettings.TeammateNameDisplay + } + + return data.Value +} diff --git a/app/notification_email.go b/app/notification_email.go index 004ee085bc..a2fbb6766d 100644 --- a/app/notification_email.go +++ b/app/notification_email.go @@ -74,12 +74,7 @@ func (a *App) sendNotificationEmail(notification *postNotification, user *model. useMilitaryTime = data.Value == "true" } - var nameFormat string - if data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT); err != nil { - nameFormat = *a.Config().TeamSettings.TeammateNameDisplay - } else { - nameFormat = data.Value - } + nameFormat := a.GetNotificationNameFormat(user) channelName := notification.GetChannelName(nameFormat, "") senderName := notification.GetSenderName(nameFormat, *a.Config().ServiceSettings.EnablePostUsernameOverride) diff --git a/app/notification_push.go b/app/notification_push.go index aea201ce6b..2b4e963186 100644 --- a/app/notification_push.go +++ b/app/notification_push.go @@ -109,12 +109,7 @@ func (a *App) sendPushNotification(notification *postNotification, user *model.U channel := notification.channel post := notification.post - var nameFormat string - if data, err := a.Srv.Store.Preference().Get(user.Id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, model.PREFERENCE_NAME_NAME_FORMAT); err != nil { - nameFormat = *a.Config().TeamSettings.TeammateNameDisplay - } else { - nameFormat = data.Value - } + nameFormat := a.GetNotificationNameFormat(user) channelName := notification.GetChannelName(nameFormat, user.Id) senderName := notification.GetSenderName(nameFormat, *cfg.ServiceSettings.EnablePostUsernameOverride) diff --git a/app/notification_test.go b/app/notification_test.go index 751e14acbc..13ee5ca588 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -1766,3 +1766,26 @@ func TestProcessText(t *testing.T) { }) } } + +func TestGetNotificationNameFormat(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + t.Run("show full name on", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.PrivacySettings.ShowFullName = true + *cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME + }) + + assert.Equal(t, model.SHOW_FULLNAME, th.App.GetNotificationNameFormat(th.BasicUser)) + }) + + t.Run("show full name off", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.PrivacySettings.ShowFullName = false + *cfg.TeamSettings.TeammateNameDisplay = model.SHOW_FULLNAME + }) + + assert.Equal(t, model.SHOW_USERNAME, th.App.GetNotificationNameFormat(th.BasicUser)) + }) +} From d6700e3b40fb7f11c300559b3c31b8799926ce3d Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Tue, 3 Sep 2019 15:44:45 -0400 Subject: [PATCH 14/47] Do not count deleted channels when checking max channel limit (#12021) --- app/channel.go | 15 +++++++++++++-- app/channel_test.go | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/app/channel.go b/app/channel.go index fb86a20051..7fad7095db 100644 --- a/app/channel.go +++ b/app/channel.go @@ -160,7 +160,7 @@ func (a *App) CreateChannelWithUser(channel *model.Channel, userId string) (*mod } // Get total number of channels on current team - count, err := a.GetNumberOfChannelsOnTeam(channel.TeamId) + count, err := a.GetNumberOfChannelsOnTeam(channel.TeamId, false) if err != nil { return nil, err } @@ -1720,12 +1720,23 @@ func (a *App) RemoveUserFromChannel(userIdToRemove string, removerUserId string, return nil } -func (a *App) GetNumberOfChannelsOnTeam(teamId string) (int, *model.AppError) { +func (a *App) GetNumberOfChannelsOnTeam(teamId string, includeDeleted bool) (int, *model.AppError) { // Get total number of channels on current team list, err := a.Srv.Store.Channel().GetTeamChannels(teamId) if err != nil { return 0, err } + + if !includeDeleted { + count := 0 + for _, channel := range *list { + if channel.DeleteAt == 0 { + count++ + } + } + return count, nil + } + return len(*list), nil } diff --git a/app/channel_test.go b/app/channel_test.go index 99eed39727..bc2cd33b37 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -1041,3 +1041,18 @@ func TestSearchChannelsForUser(t *testing.T) { searchAndCheck(t, "dev", []string{"test-dev-1", "test-dev-2", "dev-3"}) }) } + +func TestGetNumberOfChannelsOnTeam(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + th.App.DeleteChannel(th.BasicChannel, th.BasicUser.Id) + + count, err := th.App.GetNumberOfChannelsOnTeam(th.BasicTeam.Id, true) + require.Nil(t, err) + assert.Equal(t, 3, count) + + count, err = th.App.GetNumberOfChannelsOnTeam(th.BasicTeam.Id, false) + require.Nil(t, err) + assert.Equal(t, 2, count) +} From 8677596c0dc7eeed2777a6ac41b28393cba99ae3 Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Tue, 3 Sep 2019 22:11:55 +0200 Subject: [PATCH 15/47] [MM-17919] Add permission check for guest users before sending a /msg command (#11987) * Add permission check for guest users before sending a /msg command * Remove unnecessary check for IsGuest --- app/command_msg.go | 9 +++++++++ app/command_msg_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/app/command_msg.go b/app/command_msg.go index 36a88ba1a1..492ed8c727 100644 --- a/app/command_msg.go +++ b/app/command_msg.go @@ -58,6 +58,15 @@ func (me *msgProvider) DoCommand(a *App, args *model.CommandArgs, message string return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } + canSee, err := a.UserCanSeeOtherUser(args.UserId, userProfile.Id) + if err != nil { + mlog.Error(err.Error()) + return &model.CommandResponse{Text: args.T("api.command_msg.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + if !canSee { + return &model.CommandResponse{Text: args.T("api.command_msg.missing.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} + } + // Find the channel based on this user channelName := model.GetDMNameFromIds(args.UserId, userProfile.Id) diff --git a/app/command_msg_test.go b/app/command_msg_test.go index 6b5bbc6d46..c7054f4f94 100644 --- a/app/command_msg_test.go +++ b/app/command_msg_test.go @@ -62,4 +62,44 @@ func TestMsgProvider(t *testing.T) { assert.Equal(t, "", resp.Text) assert.Equal(t, "http://test.url/"+team.Name+"/channels/"+channelName, resp.GotoLocation) + + // Check that a guest user cannot message a user who is not in a channel/team with him + guest := th.CreateGuest() + user := th.CreateUser() + + th.LinkUserToTeam(user, team) + th.LinkUserToTeam(guest, th.BasicTeam) + th.AddUserToChannel(guest, th.BasicChannel) + + resp = cmd.DoCommand(th.App, &model.CommandArgs{ + T: i18n.IdentityTfunc(), + SiteURL: "http://test.url", + TeamId: th.BasicTeam.Id, + UserId: guest.Id, + Session: model.Session{ + Roles: model.SYSTEM_GUEST_ROLE_ID, + }, + }, "@"+user.Username+" hello") + + assert.Equal(t, "api.command_msg.missing.app_error", resp.Text) + assert.Equal(t, "", resp.GotoLocation) + + // Check that a guest user can message a user who is in a channel/team with him + th.LinkUserToTeam(user, th.BasicTeam) + th.AddUserToChannel(user, th.BasicChannel) + + resp = cmd.DoCommand(th.App, &model.CommandArgs{ + T: i18n.IdentityTfunc(), + SiteURL: "http://test.url", + TeamId: th.BasicTeam.Id, + UserId: guest.Id, + Session: model.Session{ + Roles: model.SYSTEM_GUEST_ROLE_ID, + }, + }, "@"+user.Username+" hello") + + channelName = model.GetDMNameFromIds(guest.Id, user.Id) + + assert.Equal(t, "", resp.Text) + assert.Equal(t, "http://test.url/"+th.BasicTeam.Name+"/channels/"+channelName, resp.GotoLocation) } From 8afbe9c6a4e15ef39898aa801679ea6176759fd0 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Tue, 3 Sep 2019 17:17:20 -0300 Subject: [PATCH 16/47] MM-18167: fix KV* helpers error handling (#12023) * MM-18167: fix KV* helpers error handling Returning a `nil` `*model.AppError` does not make a `nil` `error` interface. As a consequence, all of the KV* helper methods would always appear to fail, even if the underlying API call was successful. Fix this mismatch by explicitly assigning `appErr` in the helpers and only ever returning a `nil` `error` interface. Extend unit tests to achieve 100% coverage of the associated files. In the long run, we must change all function signatures to return the `error` interface instead of the abomination that is returning `*model.AppError` today. --- plugin/helpers_kv.go | 28 ++++++++++-- plugin/helpers_kv_test.go | 89 ++++++++++++++++++++++++++++++++------- 2 files changed, 97 insertions(+), 20 deletions(-) diff --git a/plugin/helpers_kv.go b/plugin/helpers_kv.go index b489ba7591..78efa31467 100644 --- a/plugin/helpers_kv.go +++ b/plugin/helpers_kv.go @@ -34,7 +34,12 @@ func (p *HelpersImpl) KVSetJSON(key string, value interface{}) error { return err } - return p.API.KVSet(key, data) + appErr := p.API.KVSet(key, data) + if appErr != nil { + return appErr + } + + return nil } // KVCompareAndSetJSON is a wrapper around KVCompareAndSet to simplify atomically writing a JSON object to the key value store. @@ -56,7 +61,12 @@ func (p *HelpersImpl) KVCompareAndSetJSON(key string, oldValue interface{}, newV } } - return p.API.KVCompareAndSet(key, oldData, newData) + set, appErr := p.API.KVCompareAndSet(key, oldData, newData) + if appErr != nil { + return set, appErr + } + + return set, nil } // KVCompareAndDeleteJSON is a wrapper around KVCompareAndDelete to simplify atomically deleting a JSON object from the key value store. @@ -71,7 +81,12 @@ func (p *HelpersImpl) KVCompareAndDeleteJSON(key string, oldValue interface{}) ( } } - return p.API.KVCompareAndDelete(key, oldData) + deleted, appErr := p.API.KVCompareAndDelete(key, oldData) + if appErr != nil { + return deleted, appErr + } + + return deleted, nil } // KVSetWithExpiryJSON is a wrapper around KVSetWithExpiry to simplify atomically writing a JSON object with expiry to the key value store. @@ -81,5 +96,10 @@ func (p *HelpersImpl) KVSetWithExpiryJSON(key string, value interface{}, expireI return err } - return p.API.KVSetWithExpiry(key, data, expireInSeconds) + appErr := p.API.KVSetWithExpiry(key, data, expireInSeconds) + if appErr != nil { + return appErr + } + + return nil } diff --git a/plugin/helpers_kv_test.go b/plugin/helpers_kv_test.go index 23a6d37fdf..f61033dacb 100644 --- a/plugin/helpers_kv_test.go +++ b/plugin/helpers_kv_test.go @@ -22,7 +22,7 @@ func TestKVGetJSON(t *testing.T) { ok, err := p.KVGetJSON("test-key", dat) api.AssertExpectations(t) assert.False(t, ok) - assert.NotNil(t, err) + assert.Error(t, err) assert.Nil(t, dat) }) @@ -38,7 +38,7 @@ func TestKVGetJSON(t *testing.T) { ok, err := p.KVGetJSON("test-key", dat) api.AssertExpectations(t) assert.False(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) assert.Nil(t, dat) }) @@ -54,7 +54,7 @@ func TestKVGetJSON(t *testing.T) { ok, err := p.KVGetJSON("test-key", &dat) api.AssertExpectations(t) assert.False(t, ok) - assert.NotNil(t, err) + assert.Error(t, err) assert.Nil(t, dat) }) @@ -70,7 +70,7 @@ func TestKVGetJSON(t *testing.T) { ok, err := p.KVGetJSON("test-key", &dat) assert.True(t, ok) api.AssertExpectations(t) - assert.Nil(t, err) + assert.NoError(t, err) assert.Equal(t, map[string]interface{}{ "val-a": float64(10), }, dat) @@ -86,7 +86,21 @@ func TestKVSetJSON(t *testing.T) { err := p.KVSetJSON("test-key", func() { return }) api.AssertExpectations(t) - assert.NotNil(t, err) + assert.Error(t, err) + }) + + t.Run("KVSet error", func(t *testing.T) { + api := &plugintest.API{} + api.On("KVSet", "test-key", []byte(`{"val-a":10}`)).Return(&model.AppError{}) + + p := &plugin.HelpersImpl{API: api} + + err := p.KVSetJSON("test-key", map[string]interface{}{ + "val-a": float64(10), + }) + + api.AssertExpectations(t) + assert.Error(t, err) }) t.Run("marshallable struct", func(t *testing.T) { @@ -100,7 +114,7 @@ func TestKVSetJSON(t *testing.T) { }) api.AssertExpectations(t) - assert.Nil(t, err) + assert.NoError(t, err) }) } @@ -114,7 +128,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { api.AssertExpectations(t) assert.Equal(t, false, ok) - assert.NotNil(t, err) + assert.Error(t, err) }) t.Run("new value JSON marshal error", func(t *testing.T) { @@ -127,7 +141,23 @@ func TestKVCompareAndSetJSON(t *testing.T) { api.AssertExpectations(t) assert.False(t, ok) - assert.NotNil(t, err) + assert.Error(t, err) + }) + + t.Run("KVCompareAndSet error", func(t *testing.T) { + api := &plugintest.API{} + api.On("KVCompareAndSet", "test-key", []byte(`{"val-a":10}`), []byte(`{"val-b":20}`)).Return(false, &model.AppError{}) + p := &plugin.HelpersImpl{API: api} + + ok, err := p.KVCompareAndSetJSON("test-key", map[string]interface{}{ + "val-a": 10, + }, map[string]interface{}{ + "val-b": 20, + }) + + api.AssertExpectations(t) + assert.False(t, ok) + assert.Error(t, err) }) t.Run("old value nil", func(t *testing.T) { @@ -141,7 +171,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { api.AssertExpectations(t) assert.True(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) }) t.Run("old value non-nil", func(t *testing.T) { @@ -157,7 +187,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { api.AssertExpectations(t) assert.True(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) }) t.Run("new value nil", func(t *testing.T) { @@ -171,7 +201,7 @@ func TestKVCompareAndSetJSON(t *testing.T) { api.AssertExpectations(t) assert.True(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) }) } @@ -185,7 +215,21 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { api.AssertExpectations(t) assert.Equal(t, false, ok) - assert.NotNil(t, err) + assert.Error(t, err) + }) + + t.Run("KVCompareAndDelete error", func(t *testing.T) { + api := &plugintest.API{} + api.On("KVCompareAndDelete", "test-key", []byte(`{"val-a":10}`)).Return(false, &model.AppError{}) + p := &plugin.HelpersImpl{API: api} + + ok, err := p.KVCompareAndDeleteJSON("test-key", map[string]interface{}{ + "val-a": 10, + }) + + api.AssertExpectations(t) + assert.False(t, ok) + assert.Error(t, err) }) t.Run("old value nil", func(t *testing.T) { @@ -197,7 +241,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { api.AssertExpectations(t) assert.True(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) }) t.Run("old value non-nil", func(t *testing.T) { @@ -211,7 +255,7 @@ func TestKVCompareAndDeleteJSON(t *testing.T) { api.AssertExpectations(t) assert.True(t, ok) - assert.Nil(t, err) + assert.NoError(t, err) }) } @@ -225,7 +269,20 @@ func TestKVSetWithExpiryJSON(t *testing.T) { err := p.KVSetWithExpiryJSON("test-key", func() { return }, 100) api.AssertExpectations(t) - assert.NotNil(t, err) + assert.Error(t, err) + }) + + t.Run("KVSetWithExpiry error", func(t *testing.T) { + api := &plugintest.API{} + api.On("KVSetWithExpiry", "test-key", []byte(`{"val-a":10}`), int64(100)).Return(&model.AppError{}) + p := &plugin.HelpersImpl{API: api} + + err := p.KVSetWithExpiryJSON("test-key", map[string]interface{}{ + "val-a": float64(10), + }, 100) + + api.AssertExpectations(t) + assert.Error(t, err) }) t.Run("wellformed JSON", func(t *testing.T) { @@ -239,6 +296,6 @@ func TestKVSetWithExpiryJSON(t *testing.T) { }, 100) api.AssertExpectations(t) - assert.Nil(t, err) + assert.NoError(t, err) }) } From 5aa24aedc86b7eac6166b1febbf072e755401515 Mon Sep 17 00:00:00 2001 From: "Ankit R. Gadiya" Date: Wed, 4 Sep 2019 01:48:08 +0530 Subject: [PATCH 17/47] MM-16809: Added EnsureChannel Helper (#11852) This PR adds EnsureChannel helper analogous to EnsureBot helper which creates a new channel if not exists and update the meta data of the channel if exists. Also, it will throw error if the type of existing channel do not match the type of new channel. --- plugin/client.go | 1 + plugin/helpers_channels.go | 111 ++++++++++++ plugin/helpers_channels_test.go | 297 ++++++++++++++++++++++++++++++++ 3 files changed, 409 insertions(+) create mode 100644 plugin/helpers_channels.go create mode 100644 plugin/helpers_channels_test.go diff --git a/plugin/client.go b/plugin/client.go index e445f9e1e0..d74663d1ec 100644 --- a/plugin/client.go +++ b/plugin/client.go @@ -10,6 +10,7 @@ import ( const ( INTERNAL_KEY_PREFIX = "mmi_" BOT_USER_KEY = INTERNAL_KEY_PREFIX + "botid" + CHANNEL_KEY = INTERNAL_KEY_PREFIX + "channelid" ) // Starts the serving of a Mattermost plugin over net/rpc. gRPC is not yet supported. diff --git a/plugin/helpers_channels.go b/plugin/helpers_channels.go new file mode 100644 index 0000000000..710b022303 --- /dev/null +++ b/plugin/helpers_channels.go @@ -0,0 +1,111 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package plugin + +import ( + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/utils" + "github.com/pkg/errors" +) + +func (p *HelpersImpl) EnsureChannel(channel *model.Channel) (retChannelId string, retErr error) { + // Must provide a channel with a name and teadId + if channel == nil || len(channel.Name) < 1 || len(channel.TeamId) < 1 { + return "", errors.New("passed a bad channel, nil or no name or no team id") + } + + // If we fail for any reason, this could be a race between creation of channel and + // retrieval from another EnsureChannel. Just try the basic retrieve existing again. + defer func() { + if retChannelId == "" || retErr != nil { + var err error + var channelIdBytes []byte + + err = utils.ProgressiveRetry(func() error { + channelIdBytes, err = p.API.KVGet(CHANNEL_KEY) + if err != nil { + return err + } + return nil + }) + + if err == nil && channelIdBytes != nil { + retChannelId = string(channelIdBytes) + retErr = nil + } + } + }() + + // Fetch channel ID from key value store + channelIdBytes, kvGetErr := p.API.KVGet(CHANNEL_KEY) + if kvGetErr != nil { + // Failed to retrive the value of channel + return "", errors.Wrap(kvGetErr, "failed to get channel ID") + } + + var existingChannel *model.Channel + var channelGetErr *model.AppError + + // If channel ID exists, get existing channel by ID else get it by Name + if channelIdBytes != nil { + existingChannel, channelGetErr = p.API.GetChannel(string(channelIdBytes)) + if channelGetErr != nil { + return "", errors.Wrap(channelGetErr, "failed to get channel by ID") + } + } else { + existingChannel, channelGetErr = p.API.GetChannelByName(channel.TeamId, channel.Name, false) + if channelGetErr != nil { + return "", errors.Wrap(channelGetErr, "failed to get channel by name") + } + } + + // If channel exists, update the metadata + if existingChannel != nil { + return updateChannel(p, existingChannel, channel) + } + + // Create a new channel + createdChannel, createChannelErr := p.API.CreateChannel(channel) + if createChannelErr != nil { + return "", errors.Wrap(createChannelErr, "failed to create channel") + } + + // Set the new channel id in key value store + if kvSetErr := p.API.KVSet(CHANNEL_KEY, []byte(createdChannel.Id)); kvSetErr != nil { + p.API.LogWarn("Failed to set created channel id.", "channelid", createdChannel.Id, "err", kvSetErr) + } + + return createdChannel.Id, nil +} + +func updateChannel(p *HelpersImpl, existing *model.Channel, new *model.Channel) (string, error) { + // Update metadata of the channel + if updateErr := updateChannelMeta(existing, new); updateErr != nil { + return "", errors.Wrap(updateErr, "Failed to update the metadata of existing channel") + } + + // Send the updates to API + updatedChannel, channelUpdateErr := p.API.UpdateChannel(existing) + if channelUpdateErr != nil { + return "", errors.Wrap(channelUpdateErr, "Failed to update the existing channel") + } + + // Channel exists! + return updatedChannel.Id, nil +} + +func updateChannelMeta(existing *model.Channel, new *model.Channel) error { + // Check if channels are of different types + if existing.Type != new.Type { + return errors.New("Channel type cannot be updated") + } + + // Update metadata of channel + existing.Name = new.Name + existing.DisplayName = new.DisplayName + existing.Purpose = new.Purpose + existing.Header = new.Header + + return nil +} diff --git a/plugin/helpers_channels_test.go b/plugin/helpers_channels_test.go new file mode 100644 index 0000000000..691466ed08 --- /dev/null +++ b/plugin/helpers_channels_test.go @@ -0,0 +1,297 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package plugin_test + +import ( + "testing" + + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/plugin" + "github.com/mattermost/mattermost-server/plugin/plugintest" + "github.com/stretchr/testify/assert" +) + +func TestEnsureChannel(t *testing.T) { + setupAPI := func() *plugintest.API { + return &plugintest.API{} + } + + testChannel := &model.Channel{ + Id: model.NewId(), + TeamId: model.NewId(), + Type: "public", + Name: "test_channel", + DisplayName: "Test Channel", + Purpose: "Testing EnsureChannel", + Header: "Testing EnsureChannel", + } + + t.Run("bad parameters", func(t *testing.T) { + t.Run("no channel", func(t *testing.T) { + p := &plugin.HelpersImpl{} + channelId, err := p.EnsureChannel(nil) + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("empty name", func(t *testing.T) { + p := &plugin.HelpersImpl{} + channelId, err := p.EnsureChannel(&model.Channel{ + Name: "", + }) + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("name without teamId", func(t *testing.T) { + p := &plugin.HelpersImpl{} + channelId, err := p.EnsureChannel(&model.Channel{ + Name: "test_channel", + }) + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("teamId without name", func(t *testing.T) { + p := &plugin.HelpersImpl{} + channelId, err := p.EnsureChannel(&model.Channel{ + TeamId: model.NewId(), + }) + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("teamId with empty name", func(t *testing.T) { + p := &plugin.HelpersImpl{} + channelId, err := p.EnsureChannel(&model.Channel{ + TeamId: model.NewId(), + }) + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + }) + + t.Run("if channel already exists in Key Value store", func(t *testing.T) { + t.Run("should return an error if unable to get channel id", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return an error if unable to get channel", func(t *testing.T) { + expectedChannelId := model.NewId() + + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(expectedChannelId), nil) + api.On("GetChannel", expectedChannelId).Return(nil, &model.AppError{}) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return an error if unable to update channel", func(t *testing.T) { + expectedChannelId := model.NewId() + + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(expectedChannelId), nil) + api.On("GetChannel", expectedChannelId).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(nil, &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return the Id of existing channel if metadata is same", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil) + api.On("GetChannel", testChannel.Id).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(testChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, testChannel.Id, channelId) + assert.Nil(t, err) + }) + t.Run("should return error if channel type is different from existing one", func(t *testing.T) { + privChannel := &model.Channel{ + Id: model.NewId(), + Type: "private", + TeamId: testChannel.TeamId, + Name: testChannel.Name, + } + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil) + api.On("GetChannel", testChannel.Id).Return(privChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return the Id of updated channel if metadata is different", func(t *testing.T) { + updatedChannel := &model.Channel{ + Id: model.NewId(), + TeamId: testChannel.TeamId, + Name: testChannel.Name, + } + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return([]byte(testChannel.Id), nil) + api.On("GetChannel", testChannel.Id).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(updatedChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, updatedChannel.Id, channelId) + assert.Nil(t, err) + }) + }) + + t.Run("if channel is not in Key Value store but already exists", func(t *testing.T) { + t.Run("should return an error if unable to get channel", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return the Id of existing channel if metadata is same", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(testChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, testChannel.Id, channelId) + assert.Nil(t, err) + }) + t.Run("should return error if failed to update the channel", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(nil, &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should return the Id of updated channel if metadata is different", func(t *testing.T) { + updatedChannel := &model.Channel{ + Id: model.NewId(), + TeamId: testChannel.TeamId, + Name: testChannel.Name, + } + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(testChannel, nil) + api.On("UpdateChannel", testChannel).Return(updatedChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, updatedChannel.Id, channelId) + assert.Nil(t, err) + }) + t.Run("should return error if channel type is different from existing one", func(t *testing.T) { + privChannel := &model.Channel{ + Id: model.NewId(), + Type: "private", + TeamId: testChannel.TeamId, + Name: testChannel.Name, + } + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(privChannel, nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + }) + + t.Run("if channel does not exist", func(t *testing.T) { + t.Run("should create new channel and return the Id", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil) + api.On("CreateChannel", testChannel).Return(testChannel, nil) + api.On("KVSet", plugin.CHANNEL_KEY, []byte(testChannel.Id)).Return(nil) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, testChannel.Id, channelId) + assert.Nil(t, err) + }) + t.Run("should return error if unable to create new channel", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil) + api.On("CreateChannel", testChannel).Return(nil, &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, "", channelId) + assert.NotNil(t, err) + }) + t.Run("should log and return id if unable to write to Key Value store", func(t *testing.T) { + api := setupAPI() + api.On("KVGet", plugin.CHANNEL_KEY).Return(nil, nil) + api.On("GetChannelByName", testChannel.TeamId, testChannel.Name, false).Return(nil, nil) + api.On("CreateChannel", testChannel).Return(testChannel, nil) + api.On("KVSet", plugin.CHANNEL_KEY, []byte(testChannel.Id)).Return(&model.AppError{}) + api.On("LogWarn", "Failed to set created channel id.", "channelid", testChannel.Id, "err", &model.AppError{}) + defer api.AssertExpectations(t) + + p := &plugin.HelpersImpl{API: api} + + channelId, err := p.EnsureChannel(testChannel) + + assert.Equal(t, testChannel.Id, channelId) + assert.Nil(t, err) + }) + }) +} From 65f03de1ee49f831f12dd9bbf28a512f03d6e80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Villablanca=20V=C3=A1squez?= Date: Tue, 3 Sep 2019 18:41:52 -0400 Subject: [PATCH 18/47] [MM-19711] - Get plugin configuration without removing secrets (#11928) * Method GetUnsanitizedConfig() exposed to API (Plugin) interface and his implementations * improvements with some suggestions * Fix documentation (final period added) Co-Authored-By: Ali Farooq <25732808+ali-farooq0@users.noreply.github.com> * Added some test for Plugin.GetConfig and Plugin.GetUnsanitizedConfig * Removed empty lines --- app/plugin_api.go | 5 +++ app/plugin_api_test.go | 74 ++++++++++++++++++++++++++++++++++ plugin/api.go | 5 +++ plugin/client_rpc_generated.go | 27 +++++++++++++ plugin/plugintest/api.go | 22 +++++++++- plugin/plugintest/helpers.go | 6 ++- plugin/plugintest/hooks.go | 15 ++++--- 7 files changed, 145 insertions(+), 9 deletions(-) diff --git a/app/plugin_api.go b/app/plugin_api.go index a99067fcb4..05ca69ab8b 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -81,6 +81,11 @@ func (api *PluginAPI) GetConfig() *model.Config { return api.app.GetSanitizedConfig() } +// GetUnsanitizedConfig gets the configuration for a system admin without removing secrets. +func (api *PluginAPI) GetUnsanitizedConfig() *model.Config { + return api.app.Config().Clone() +} + func (api *PluginAPI) SaveConfig(config *model.Config) *model.AppError { return api.app.SaveConfig(config, true) } diff --git a/app/plugin_api_test.go b/app/plugin_api_test.go index 5f3a756016..8277a2897b 100644 --- a/app/plugin_api_test.go +++ b/app/plugin_api_test.go @@ -1331,3 +1331,77 @@ func TestPluginCreatePostWithUploadedFile(t *testing.T) { require.Nil(t, err) assert.Equal(t, model.StringArray{fileInfo.Id}, actualPost.FileIds) } + +func TestPluginAPIGetConfig(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + config := api.GetConfig() + if config.LdapSettings.BindPassword != nil && len(*config.LdapSettings.BindPassword) > 0 { + assert.Equal(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING) + } + + assert.Equal(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING) + + if len(*config.FileSettings.AmazonS3SecretAccessKey) > 0 { + assert.Equal(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING) + } + + if config.EmailSettings.SMTPPassword != nil && len(*config.EmailSettings.SMTPPassword) > 0 { + assert.Equal(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING) + } + + if len(*config.GitLabSettings.Secret) > 0 { + assert.Equal(t, *config.GitLabSettings.Secret, model.FAKE_SETTING) + } + + assert.Equal(t, *config.SqlSettings.DataSource, model.FAKE_SETTING) + assert.Equal(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING) + assert.Equal(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING) + + for i := range config.SqlSettings.DataSourceReplicas { + assert.Equal(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING) + } + + for i := range config.SqlSettings.DataSourceSearchReplicas { + assert.Equal(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING) + } +} + +func TestPluginAPIGetUnsanitizedConfig(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + api := th.SetupPluginAPI() + + config := api.GetUnsanitizedConfig() + if config.LdapSettings.BindPassword != nil && len(*config.LdapSettings.BindPassword) > 0 { + assert.NotEqual(t, *config.LdapSettings.BindPassword, model.FAKE_SETTING) + } + + assert.NotEqual(t, *config.FileSettings.PublicLinkSalt, model.FAKE_SETTING) + + if len(*config.FileSettings.AmazonS3SecretAccessKey) > 0 { + assert.NotEqual(t, *config.FileSettings.AmazonS3SecretAccessKey, model.FAKE_SETTING) + } + + if config.EmailSettings.SMTPPassword != nil && len(*config.EmailSettings.SMTPPassword) > 0 { + assert.NotEqual(t, *config.EmailSettings.SMTPPassword, model.FAKE_SETTING) + } + + if len(*config.GitLabSettings.Secret) > 0 { + assert.NotEqual(t, *config.GitLabSettings.Secret, model.FAKE_SETTING) + } + + assert.NotEqual(t, *config.SqlSettings.DataSource, model.FAKE_SETTING) + assert.NotEqual(t, *config.SqlSettings.AtRestEncryptKey, model.FAKE_SETTING) + assert.NotEqual(t, *config.ElasticsearchSettings.Password, model.FAKE_SETTING) + + for i := range config.SqlSettings.DataSourceReplicas { + assert.NotEqual(t, config.SqlSettings.DataSourceReplicas[i], model.FAKE_SETTING) + } + + for i := range config.SqlSettings.DataSourceSearchReplicas { + assert.NotEqual(t, config.SqlSettings.DataSourceSearchReplicas[i], model.FAKE_SETTING) + } +} diff --git a/plugin/api.go b/plugin/api.go index fed0bbb3df..c799b47f57 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -31,6 +31,11 @@ type API interface { // GetConfig fetches the currently persisted config GetConfig() *model.Config + // GetUnsanitizedConfig fetches the currently persisted config without removing secrets. + // + // Minimum server version: 5.16 + GetUnsanitizedConfig() *model.Config + // SaveConfig sets the given config and persists the changes SaveConfig(config *model.Config) *model.AppError diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 22b577f3c5..e1bd6165ff 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -588,6 +588,33 @@ func (s *apiRPCServer) GetConfig(args *Z_GetConfigArgs, returns *Z_GetConfigRetu return nil } +type Z_GetUnsanitizedConfigArgs struct { +} + +type Z_GetUnsanitizedConfigReturns struct { + A *model.Config +} + +func (g *apiRPCClient) GetUnsanitizedConfig() *model.Config { + _args := &Z_GetUnsanitizedConfigArgs{} + _returns := &Z_GetUnsanitizedConfigReturns{} + if err := g.client.Call("Plugin.GetUnsanitizedConfig", _args, _returns); err != nil { + log.Printf("RPC call to GetUnsanitizedConfig API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) GetUnsanitizedConfig(args *Z_GetUnsanitizedConfigArgs, returns *Z_GetUnsanitizedConfigReturns) error { + if hook, ok := s.impl.(interface { + GetUnsanitizedConfig() *model.Config + }); ok { + returns.A = hook.GetUnsanitizedConfig() + } else { + return encodableError(fmt.Errorf("API GetUnsanitizedConfig called but not implemented.")) + } + return nil +} + type Z_SaveConfigArgs struct { A *model.Config } diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index f634748a1e..e5f92671b2 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -4,8 +4,10 @@ package plugintest -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // API is an autogenerated mock type for the API type type API struct { @@ -1636,6 +1638,22 @@ func (_m *API) GetTeamsUnreadForUser(userId string) ([]*model.TeamUnread, *model return r0, r1 } +// GetUnsanitizedConfig provides a mock function with given fields: +func (_m *API) GetUnsanitizedConfig() *model.Config { + ret := _m.Called() + + var r0 *model.Config + if rf, ok := ret.Get(0).(func() *model.Config); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Config) + } + } + + return r0 +} + // GetUser provides a mock function with given fields: userId func (_m *API) GetUser(userId string) (*model.User, *model.AppError) { ret := _m.Called(userId) diff --git a/plugin/plugintest/helpers.go b/plugin/plugintest/helpers.go index ba3f92b33a..066c0b2b8c 100644 --- a/plugin/plugintest/helpers.go +++ b/plugin/plugintest/helpers.go @@ -4,8 +4,10 @@ package plugintest -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // Helpers is an autogenerated mock type for the Helpers type type Helpers struct { diff --git a/plugin/plugintest/hooks.go b/plugin/plugintest/hooks.go index 5ebeecc42e..ef317bf4e2 100644 --- a/plugin/plugintest/hooks.go +++ b/plugin/plugintest/hooks.go @@ -4,11 +4,16 @@ package plugintest -import http "net/http" -import io "io" -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" -import plugin "github.com/mattermost/mattermost-server/plugin" +import ( + io "io" + http "net/http" + + mock "github.com/stretchr/testify/mock" + + model "github.com/mattermost/mattermost-server/model" + + plugin "github.com/mattermost/mattermost-server/plugin" +) // Hooks is an autogenerated mock type for the Hooks type type Hooks struct { From 5a477a617a5a72cfae44972578ec8f2aab471b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Vay=C3=A1?= Date: Wed, 4 Sep 2019 12:25:36 +0200 Subject: [PATCH 19/47] [MM-17578]Default download link to anchor for apps (#11931) --- model/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/config.go b/model/config.go index 5236937076..9bc4376b5b 100644 --- a/model/config.go +++ b/model/config.go @@ -138,7 +138,7 @@ const ( SAML_SETTINGS_DEFAULT_LOCALE_ATTRIBUTE = "" SAML_SETTINGS_DEFAULT_POSITION_ATTRIBUTE = "" - NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://about.mattermost.com/downloads/" + NATIVEAPP_SETTINGS_DEFAULT_APP_DOWNLOAD_LINK = "https://mattermost.com/download/#mattermostApps" NATIVEAPP_SETTINGS_DEFAULT_ANDROID_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-android-app/" NATIVEAPP_SETTINGS_DEFAULT_IOS_APP_DOWNLOAD_LINK = "https://about.mattermost.com/mattermost-ios-app/" From b9a3c2e90ed6d59c04bdd982833b5cba2eff9643 Mon Sep 17 00:00:00 2001 From: Woolim Cho Date: Wed, 4 Sep 2019 20:41:29 +0900 Subject: [PATCH 20/47] MM-10272: Add a count for pinned posts header icon (#11840) --- api4/channel.go | 8 ++- api4/channel_test.go | 10 ++++ app/channel.go | 4 ++ app/web_hub.go | 1 + i18n/en.json | 4 ++ model/channel_stats.go | 7 +-- store/sqlstore/channel_store.go | 66 ++++++++++++++++++++++++ store/store.go | 3 ++ store/storetest/channel_store.go | 73 +++++++++++++++++++++++++++ store/storetest/mocks/ChannelStore.go | 42 +++++++++++++++ store/timer_layer.go | 17 +++++++ 11 files changed, 231 insertions(+), 4 deletions(-) diff --git a/api4/channel.go b/api4/channel.go index bc7bb9c0b5..03edc36a7b 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -527,7 +527,13 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) { return } - stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount, GuestCount: guestCount} + pinnedPostCount, err := c.App.GetChannelPinnedPostCount(c.Params.ChannelId) + if err != nil { + c.Err = err + return + } + + stats := model.ChannelStats{ChannelId: c.Params.ChannelId, MemberCount: memberCount, GuestCount: guestCount, PinnedPostCount: pinnedPostCount} w.Write([]byte(stats.ToJson())) } diff --git a/api4/channel_test.go b/api4/channel_test.go index 8473f8c587..fb7ac11d15 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1849,6 +1849,16 @@ func TestGetChannelStats(t *testing.T) { t.Fatal("couldnt't get extra info") } else if stats.MemberCount != 1 { t.Fatal("got incorrect member count") + } else if stats.PinnedPostCount != 0 { + t.Fatal("got incorrect pinned post count") + } + + th.CreatePinnedPostWithClient(th.Client, channel) + stats, resp = Client.GetChannelStats(channel.Id, "") + CheckNoError(t, resp) + + if stats.PinnedPostCount != 1 { + t.Fatal("should have returned 1 pinned post count") } _, resp = Client.GetChannelStats("junk", "") diff --git a/app/channel.go b/app/channel.go index 7fad7095db..c9388da7b1 100644 --- a/app/channel.go +++ b/app/channel.go @@ -1330,6 +1330,10 @@ func (a *App) GetChannelGuestCount(channelId string) (int64, *model.AppError) { return a.Srv.Store.Channel().GetGuestCount(channelId, true) } +func (a *App) GetChannelPinnedPostCount(channelId string) (int64, *model.AppError) { + return a.Srv.Store.Channel().GetPinnedPostCount(channelId, true) +} + func (a *App) GetChannelCounts(teamId string, userId string) (*model.ChannelCounts, *model.AppError) { return a.Srv.Store.Channel().GetChannelCounts(teamId, userId) } diff --git a/app/web_hub.go b/app/web_hub.go index babf49851b..8c5e828c1e 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -289,6 +289,7 @@ func (a *App) InvalidateCacheForChannelPosts(channelId string) { func (a *App) InvalidateCacheForChannelPostsSkipClusterSend(channelId string) { a.Srv.Store.Post().InvalidateLastPostTimeCache(channelId) + a.Srv.Store.Channel().InvalidatePinnedPostCount(channelId) } func (a *App) InvalidateCacheForUser(userId string) { diff --git a/i18n/en.json b/i18n/en.json index af1be7e046..71256f8f08 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -5658,6 +5658,10 @@ "id": "store.sql_channel.get_more_channels.get.app_error", "translation": "Unable to get the channels" }, + { + "id": "store.sql_channel.get_pinnedpost_count.app_error", + "translation": "Unable to get the channel pinned post count" + }, { "id": "store.sql_channel.get_public_channels.get.app_error", "translation": "Unable to get public channels" diff --git a/model/channel_stats.go b/model/channel_stats.go index 5eb26b7e29..63367fa48f 100644 --- a/model/channel_stats.go +++ b/model/channel_stats.go @@ -9,9 +9,10 @@ import ( ) type ChannelStats struct { - ChannelId string `json:"channel_id"` - MemberCount int64 `json:"member_count"` - GuestCount int64 `json:"guest_count"` + ChannelId string `json:"channel_id"` + MemberCount int64 `json:"member_count"` + GuestCount int64 `json:"guest_count"` + PinnedPostCount int64 `json:"pinnedpost_count"` } func (o *ChannelStats) ToJson() string { diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 5ff8af8513..ae01300781 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -35,6 +35,9 @@ const ( CHANNEL_GUESTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE CHANNEL_GUESTS_COUNTS_CACHE_SEC = 1800 // 30 mins + CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE = model.CHANNEL_CACHE_SIZE + CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SEC = 1800 // 30 mins + CHANNEL_CACHE_SEC = 900 // 15 mins ) @@ -281,6 +284,7 @@ type publicChannel struct { } var channelMemberCountsCache = utils.NewLru(CHANNEL_MEMBERS_COUNTS_CACHE_SIZE) +var channelPinnedPostCountsCache = utils.NewLru(CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SIZE) var channelGuestCountsCache = utils.NewLru(CHANNEL_GUESTS_COUNTS_CACHE_SIZE) var allChannelMembersForUserCache = utils.NewLru(ALL_CHANNEL_MEMBERS_FOR_USER_CACHE_SIZE) var allChannelMembersNotifyPropsForChannelCache = utils.NewLru(ALL_CHANNEL_MEMBERS_NOTIFY_PROPS_FOR_CHANNEL_CACHE_SIZE) @@ -289,6 +293,7 @@ var channelByNameCache = utils.NewLru(model.CHANNEL_CACHE_SIZE) func (s SqlChannelStore) ClearCaches() { channelMemberCountsCache.Purge() + channelPinnedPostCountsCache.Purge() channelGuestCountsCache.Purge() allChannelMembersForUserCache.Purge() allChannelMembersNotifyPropsForChannelCache.Purge() @@ -297,6 +302,7 @@ func (s SqlChannelStore) ClearCaches() { if s.metrics != nil { s.metrics.IncrementMemCacheInvalidationCounter("Channel Member Counts - Purge") + s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Purge") s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members for User - Purge") s.metrics.IncrementMemCacheInvalidationCounter("All Channel Members Notify Props for Channel - Purge") s.metrics.IncrementMemCacheInvalidationCounter("Channel - Purge") @@ -1626,6 +1632,66 @@ func (s SqlChannelStore) GetMemberCount(channelId string, allowFromCache bool) ( return count, nil } +func (s SqlChannelStore) InvalidatePinnedPostCount(channelId string) { + channelPinnedPostCountsCache.Remove(channelId) + if s.metrics != nil { + s.metrics.IncrementMemCacheInvalidationCounter("Channel Pinned Post Counts - Remove by ChannelId") + } +} + +func (s SqlChannelStore) GetPinnedPostCountFromCache(channelId string) int64 { + if cacheItem, ok := channelPinnedPostCountsCache.Get(channelId); ok { + if s.metrics != nil { + s.metrics.IncrementMemCacheHitCounter("Channel Pinned Post Counts") + } + return cacheItem.(int64) + } + + if s.metrics != nil { + s.metrics.IncrementMemCacheMissCounter("Channel Pinned Post Counts") + } + + count, err := s.GetPinnedPostCount(channelId, true) + if err != nil { + return 0 + } + + return count +} + +func (s SqlChannelStore) GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError) { + if allowFromCache { + if cacheItem, ok := channelPinnedPostCountsCache.Get(channelId); ok { + if s.metrics != nil { + s.metrics.IncrementMemCacheHitCounter("Channel Pinned Post Counts") + } + return cacheItem.(int64), nil + } + } + + if s.metrics != nil { + s.metrics.IncrementMemCacheMissCounter("Channel Pinned Post Counts") + } + + count, err := s.GetReplica().SelectInt(` + SELECT count(*) + FROM Posts + WHERE + IsPinned = true + AND ChannelId = :ChannelId + AND DeleteAt = 0`, map[string]interface{}{"ChannelId": channelId}) + + if err != nil { + return 0, model.NewAppError("SqlChannelStore.GetPinnedPostCount", "store.sql_channel.get_pinnedpost_count.app_error", nil, "channel_id="+channelId+", "+err.Error(), http.StatusInternalServerError) + } + + if allowFromCache { + channelPinnedPostCountsCache.AddWithExpiresInSecs(channelId, count, CHANNEL_PINNEDPOSTS_COUNTS_CACHE_SEC) + } + + return count, nil +} + func (s SqlChannelStore) InvalidateGuestCount(channelId string) { channelGuestCountsCache.Remove(channelId) if s.metrics != nil { diff --git a/store/store.go b/store/store.go index 76f87fb0b0..a098317a0b 100644 --- a/store/store.go +++ b/store/store.go @@ -148,6 +148,9 @@ type ChannelStore interface { InvalidateMemberCount(channelId string) GetMemberCountFromCache(channelId string) int64 GetMemberCount(channelId string, allowFromCache bool) (int64, *model.AppError) + InvalidatePinnedPostCount(channelId string) + GetPinnedPostCountFromCache(channelId string) int64 + GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError) InvalidateGuestCount(channelId string) GetGuestCountFromCache(channelId string) int64 GetGuestCount(channelId string, allowFromCache bool) (int64, *model.AppError) diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index edf4531a75..e45a34ea06 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -75,6 +75,7 @@ func TestChannelStore(t *testing.T, ss store.Store, s SqlSupplier) { t.Run("SearchGroupChannels", func(t *testing.T) { testChannelStoreSearchGroupChannels(t, ss) }) t.Run("AnalyticsDeletedTypeCount", func(t *testing.T) { testChannelStoreAnalyticsDeletedTypeCount(t, ss) }) t.Run("GetPinnedPosts", func(t *testing.T) { testChannelStoreGetPinnedPosts(t, ss) }) + t.Run("GetPinnedPostCount", func(t *testing.T) { testChannelStoreGetPinnedPostCount(t, ss) }) t.Run("MaxChannelsPerTeam", func(t *testing.T) { testChannelStoreMaxChannelsPerTeam(t, ss) }) t.Run("GetChannelsByScheme", func(t *testing.T) { testChannelStoreGetChannelsByScheme(t, ss) }) t.Run("MigrateChannelMembers", func(t *testing.T) { testChannelStoreMigrateChannelMembers(t, ss) }) @@ -3379,6 +3380,78 @@ func testChannelStoreGetPinnedPosts(t *testing.T, ss store.Store) { } } +func testChannelStoreGetPinnedPostCount(t *testing.T, ss store.Store) { + ch1 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "Name", + Name: "zz" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + } + + o1, err := ss.Channel().Save(ch1, -1) + require.Nil(t, err) + + _, err = ss.Post().Save(&model.Post{ + UserId: model.NewId(), + ChannelId: o1.Id, + Message: "test", + IsPinned: true, + }) + require.Nil(t, err) + + _, err = ss.Post().Save(&model.Post{ + UserId: model.NewId(), + ChannelId: o1.Id, + Message: "test", + IsPinned: true, + }) + require.Nil(t, err) + + if count, errGet := ss.Channel().GetPinnedPostCount(o1.Id, true); errGet != nil { + t.Fatal(errGet) + } else if count != 2 { + t.Fatal("didn't return right count") + } + + if ss.Channel().GetPinnedPostCountFromCache(o1.Id) != 2 { + t.Fatal("should have saved 2 pinned post count ") + } + + ch2 := &model.Channel{ + TeamId: model.NewId(), + DisplayName: "Name", + Name: "zz" + model.NewId() + "b", + Type: model.CHANNEL_OPEN, + } + + o2, err := ss.Channel().Save(ch2, -1) + require.Nil(t, err) + + _, err = ss.Post().Save(&model.Post{ + UserId: model.NewId(), + ChannelId: o2.Id, + Message: "test", + }) + require.Nil(t, err) + + _, err = ss.Post().Save(&model.Post{ + UserId: model.NewId(), + ChannelId: o2.Id, + Message: "test", + }) + require.Nil(t, err) + + if count, errGet := ss.Channel().GetPinnedPostCount(o2.Id, true); errGet != nil { + t.Fatal(errGet) + } else if count != 0 { + t.Fatal("should return 0") + } + + if ss.Channel().GetPinnedPostCountFromCache(o2.Id) != 0 { + t.Fatal("should have saved 0 pinned post count ") + } +} + func testChannelStoreMaxChannelsPerTeam(t *testing.T, ss store.Store) { channel := &model.Channel{ TeamId: model.NewId(), diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index 9c78f72efd..86f258a318 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -993,6 +993,43 @@ func (_m *ChannelStore) GetMoreChannels(teamId string, userId string, offset int return r0, r1 } +// GetPinnedPostCount provides a mock function with given fields: channelId, allowFromCache +func (_m *ChannelStore) GetPinnedPostCount(channelId string, allowFromCache bool) (int64, *model.AppError) { + ret := _m.Called(channelId, allowFromCache) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string, bool) int64); ok { + r0 = rf(channelId, allowFromCache) + } else { + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(string, bool) *model.AppError); ok { + r1 = rf(channelId, allowFromCache) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// GetPinnedPostCountFromCache provides a mock function with given fields: channelId +func (_m *ChannelStore) GetPinnedPostCountFromCache(channelId string) int64 { + ret := _m.Called(channelId) + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(channelId) + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + // GetPinnedPosts provides a mock function with given fields: channelId func (_m *ChannelStore) GetPinnedPosts(channelId string) (*model.PostList, *model.AppError) { ret := _m.Called(channelId) @@ -1139,6 +1176,11 @@ func (_m *ChannelStore) InvalidateMemberCount(channelId string) { _m.Called(channelId) } +// InvalidatePinnedPostCount provides a mock function with given fields: channelId +func (_m *ChannelStore) InvalidatePinnedPostCount(channelId string) { + _m.Called(channelId) +} + // IsUserInChannelUseCache provides a mock function with given fields: userId, channelId func (_m *ChannelStore) IsUserInChannelUseCache(userId string, channelId string) bool { ret := _m.Called(userId, channelId) diff --git a/store/timer_layer.go b/store/timer_layer.go index 17848f300f..da3bcb5a8c 100644 --- a/store/timer_layer.go +++ b/store/timer_layer.go @@ -1382,6 +1382,23 @@ func (s *TimerLayerChannelStore) InvalidateMemberCount(channelId string) { return } +func (s *TimerLayerChannelStore) InvalidatePinnedPostCount(channelId string) { + start := timemodule.Now() + + s.ChannelStore.InvalidatePinnedPostCount(channelId) + + t := timemodule.Now() + elapsed := t.Sub(start) + if s.Root.Metrics != nil { + success := "false" + if true { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("ChannelStore.InvalidatePinnedPostCount", success, float64(elapsed)) + } + return +} + func (s *TimerLayerChannelStore) IsUserInChannelUseCache(userId string, channelId string) bool { start := timemodule.Now() From 0ddc5f4a3245cee4db737da0f3e8772f5b92e232 Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Thu, 5 Sep 2019 12:50:40 +0200 Subject: [PATCH 21/47] upgrade db to 5.15 (#11918) --- model/version.go | 1 + store/sqlstore/upgrade.go | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/model/version.go b/model/version.go index 0002932345..6fc625b95b 100644 --- a/model/version.go +++ b/model/version.go @@ -13,6 +13,7 @@ import ( // It should be maintained in chronological order with most current // release at the front of the list. var versions = []string{ + "5.15.0", "5.14.0", "5.13.0", "5.12.0", diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index 603b57ecfb..e118f4e2a6 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -729,11 +729,9 @@ func UpgradeDatabaseToVersion514(sqlStore SqlStore) { } func UpgradeDatabaseToVersion515(sqlStore SqlStore) { - // TODO: Uncomment following condition when version 5.15.0 is released - // if shouldPerformUpgrade(sqlStore, VERSION_5_14_0, VERSION_5_15_0) { - - // saveSchemaVersion(sqlStore, VERSION_5_15_0) - // } + if shouldPerformUpgrade(sqlStore, VERSION_5_14_0, VERSION_5_15_0) { + saveSchemaVersion(sqlStore, VERSION_5_15_0) + } } func UpgradeDatabaseToVersion516(sqlStore SqlStore) { From 08d3f3219d7ecee1c297d4a5fe98d874cd889fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 5 Sep 2019 17:20:58 +0200 Subject: [PATCH 22/47] Converting to structured logging the file services/mailservice/mail.go (#12038) * Converting to structured logging the file services/mailservice/mail.go * Using snake_case instead of CamelCase * Addressing PR review comments --- services/mailservice/mail.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/services/mailservice/mail.go b/services/mailservice/mail.go index 6cff92234d..6384257e4b 100644 --- a/services/mailservice/mail.go +++ b/services/mailservice/mail.go @@ -6,7 +6,6 @@ package mailservice import ( "crypto/tls" "errors" - "fmt" "io" "mime" "net" @@ -139,14 +138,14 @@ func ConnectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { func NewSMTPClientAdvanced(conn net.Conn, hostname string, connectionInfo *SmtpConnectionInfo) (*smtp.Client, *model.AppError) { c, err := smtp.NewClient(conn, connectionInfo.SmtpServerName+":"+connectionInfo.SmtpPort) if err != nil { - mlog.Error(fmt.Sprintf("Failed to open a connection to SMTP server %v", err)) + mlog.Error("Failed to open a connection to SMTP server", mlog.Err(err)) return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.open_tls.app_error", nil, err.Error(), http.StatusInternalServerError) } if hostname != "" { err = c.Hello(hostname) if err != nil { - mlog.Error(fmt.Sprintf("Failed to to set the HELO to SMTP server %v", err)) + mlog.Error("Failed to to set the HELO to SMTP server", mlog.Err(err)) return nil, model.NewAppError("SendMail", "utils.mail.connect_smtp.helo.app_error", nil, err.Error(), http.StatusInternalServerError) } } @@ -191,14 +190,14 @@ func TestConnection(config *model.Config) { conn, err1 := ConnectToSMTPServer(config) if err1 != nil { - mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err1.Message), err1.DetailedError)) + mlog.Error("SMTP server settings do not appear to be configured properly", mlog.Err(err1)) return } defer conn.Close() c, err2 := NewSMTPClient(conn, config) if err2 != nil { - mlog.Error(fmt.Sprintf("SMTP server settings do not appear to be configured properly err=%v details=%v", utils.T(err2.Message), err2.DetailedError)) + mlog.Error("SMTP server settings do not appear to be configured properly", mlog.Err(err2)) return } defer c.Quit() @@ -240,13 +239,13 @@ func SendMailUsingConfigAdvanced(mimeTo, smtpTo string, from, replyTo mail.Addre } func SendMail(c smtpClient, mimeTo, smtpTo string, from, replyTo mail.Address, subject, htmlBody string, attachments []*model.FileInfo, mimeHeaders map[string]string, fileBackend filesstore.FileBackend, date time.Time) *model.AppError { - mlog.Debug(fmt.Sprintf("sending mail to %v with subject of '%v'", smtpTo, subject)) + mlog.Debug("sending mail", mlog.String("to", smtpTo), mlog.String("subject", subject)) htmlMessage := "\r\n" + htmlBody + "" txtBody, err := html2text.FromString(htmlBody) if err != nil { - mlog.Warn(fmt.Sprint(err)) + mlog.Warn("Unable to convert html body to text", mlog.Err(err)) txtBody = "" } From 9d937e7f1d3ea11b6244b19cf0d1c97a520efba9 Mon Sep 17 00:00:00 2001 From: Michael Kochell Date: Thu, 5 Sep 2019 13:46:10 -0400 Subject: [PATCH 23/47] bump jira plugin version to 2.1.1 (#12045) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9598d36009..510aa0b37c 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ PLUGIN_PACKAGES += mattermost-plugin-github-v0.10.2 PLUGIN_PACKAGES += mattermost-plugin-welcomebot-v1.1.0 PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.0.2 PLUGIN_PACKAGES += mattermost-plugin-antivirus-v0.1.1 -PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.0 +PLUGIN_PACKAGES += mattermost-plugin-jira-v2.1.1 PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.0.0 From 451982f9d3993df5ff60b76f82c4fcf605097493 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 5 Sep 2019 17:27:36 -0300 Subject: [PATCH 24/47] improved OnDeactivate handling (#11988) * Deactivate plugins in parallel to improve shutdown time * Give plugins at most 10s to handle OnDeactivate before forcefully terminating --- app/plugin_shutdown_test.go | 69 +++++++++++++++++++++++++++++++++++++ plugin/environment.go | 32 +++++++++++++++-- 2 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 app/plugin_shutdown_test.go diff --git a/app/plugin_shutdown_test.go b/app/plugin_shutdown_test.go new file mode 100644 index 0000000000..2f99ca8231 --- /dev/null +++ b/app/plugin_shutdown_test.go @@ -0,0 +1,69 @@ +package app + +import ( + "testing" + "time" +) + +func TestPluginShutdownTest(t *testing.T) { + if testing.Short() { + t.Skip("skipping test to verify forced shutdown of slow plugin") + } + + th := Setup(t).InitBasic() + defer th.TearDown() + + tearDown, _, _ := SetAppEnvironmentWithPlugins(t, + []string{ + ` + package main + + import ( + "github.com/mattermost/mattermost-server/plugin" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `, + ` + package main + + import ( + "github.com/mattermost/mattermost-server/plugin" + ) + + type MyPlugin struct { + plugin.MattermostPlugin + } + + func (p *MyPlugin) OnDeactivate() error { + c := make(chan bool) + <-c + + return nil + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `, + }, th.App, th.App.NewPluginAPI) + defer tearDown() + + done := make(chan bool) + go func() { + defer close(done) + th.App.ShutDownPlugins() + }() + + select { + case <-done: + case <-time.After(15 * time.Second): + t.Fatal("failed to force plugin shutdown after 10 seconds") + } +} diff --git a/plugin/environment.go b/plugin/environment.go index a6345579d6..e4410cf26a 100644 --- a/plugin/environment.go +++ b/plugin/environment.go @@ -288,16 +288,42 @@ func (env *Environment) Shutdown() { env.pluginHealthCheckJob.Cancel() } + var wg sync.WaitGroup env.registeredPlugins.Range(func(key, value interface{}) bool { rp := value.(*registeredPlugin) - if rp.supervisor != nil { + if rp.supervisor == nil { + return true + } + + wg.Add(1) + + done := make(chan bool) + go func() { + defer close(done) if err := rp.supervisor.Hooks().OnDeactivate(); err != nil { env.logger.Error("Plugin OnDeactivate() error", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id), mlog.Err(err)) } - rp.supervisor.Shutdown() - } + }() + go func() { + defer wg.Done() + + select { + case <-time.After(10 * time.Second): + env.logger.Warn("Plugin OnDeactivate() failed to complete in 10 seconds", mlog.String("plugin_id", rp.BundleInfo.Manifest.Id)) + case <-done: + } + + rp.supervisor.Shutdown() + }() + + return true + }) + + wg.Wait() + + env.registeredPlugins.Range(func(key, value interface{}) bool { env.registeredPlugins.Delete(key) return true From f9d9dd8b603a462814e204ce333e0118e2476851 Mon Sep 17 00:00:00 2001 From: threepwood-mm <54032291+threepwood-mm@users.noreply.github.com> Date: Fri, 6 Sep 2019 12:52:14 +0200 Subject: [PATCH 25/47] [MM-14521] New endpoint to validate site urls (#11944) --- api4/system.go | 27 +++++++++++++++++++++++++++ api4/system_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ app/admin.go | 11 +++++++++++ i18n/en.json | 4 ++++ model/client4.go | 16 ++++++++++++++++ 5 files changed, 100 insertions(+) diff --git a/api4/system.go b/api4/system.go index fba02a2e6e..aa587b352c 100644 --- a/api4/system.go +++ b/api4/system.go @@ -27,6 +27,7 @@ func (api *API) InitSystem() { api.BaseRoutes.ApiRoot.Handle("/audits", api.ApiSessionRequired(getAudits)).Methods("GET") api.BaseRoutes.ApiRoot.Handle("/email/test", api.ApiSessionRequired(testEmail)).Methods("POST") + api.BaseRoutes.ApiRoot.Handle("/site_url/test", api.ApiSessionRequired(testSiteURL)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/file/s3_test", api.ApiSessionRequired(testS3)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/database/recycle", api.ApiSessionRequired(databaseRecycle)).Methods("POST") api.BaseRoutes.ApiRoot.Handle("/caches/invalidate", api.ApiSessionRequired(invalidateCaches)).Methods("POST") @@ -145,6 +146,32 @@ func testEmail(c *Context, w http.ResponseWriter, r *http.Request) { ReturnStatusOK(w) } +func testSiteURL(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { + c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) + return + } + + if *c.App.Config().ExperimentalSettings.RestrictSystemAdmin { + c.Err = model.NewAppError("testSiteURL", "api.restricted_system_admin", nil, "", http.StatusForbidden) + return + } + + props := model.MapFromJson(r.Body) + siteURL := props["site_url"] + if siteURL == "" { + c.SetInvalidParam("site_url") + return + } + err := c.App.TestSiteURL(siteURL) + if err != nil { + c.Err = err + return + } + + ReturnStatusOK(w) +} + func getAudits(c *Context, w http.ResponseWriter, r *http.Request) { if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM) diff --git a/api4/system_test.go b/api4/system_test.go index 824cf0f86f..6e58b56413 100644 --- a/api4/system_test.go +++ b/api4/system_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" "github.com/mattermost/mattermost-server/mlog" @@ -149,6 +150,47 @@ func TestEmailTest(t *testing.T) { }) } +func TestSiteURLTest(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + Client := th.Client + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/valid/api/v4/system/ping") { + w.WriteHeader(200) + } else { + w.WriteHeader(400) + } + })) + defer ts.Close() + + validSiteURL := ts.URL + "/valid" + invalidSiteURL := ts.URL + "/invalid" + + t.Run("as system admin", func(t *testing.T) { + _, resp := th.SystemAdminClient.TestSiteURL("") + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.TestSiteURL(invalidSiteURL) + CheckBadRequestStatus(t, resp) + + _, resp = th.SystemAdminClient.TestSiteURL(validSiteURL) + CheckOKStatus(t, resp) + }) + + t.Run("as system user", func(t *testing.T) { + _, resp := Client.TestSiteURL(validSiteURL) + CheckForbiddenStatus(t, resp) + }) + + t.Run("as restricted system admin", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true }) + + _, resp := Client.TestSiteURL(validSiteURL) + CheckForbiddenStatus(t, resp) + }) +} + func TestDatabaseRecycle(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/app/admin.go b/app/admin.go index f883a550dd..91898efe92 100644 --- a/app/admin.go +++ b/app/admin.go @@ -4,6 +4,7 @@ package app import ( + "fmt" "io" "os" "time" @@ -174,6 +175,16 @@ func (a *App) RecycleDatabaseConnection() { mlog.Warn("Finished recycling the database connection.") } +func (a *App) TestSiteURL(siteURL string) *model.AppError { + url := fmt.Sprintf("%s/api/v4/system/ping", siteURL) + res, err := http.Get(url) + if err != nil || res.StatusCode != 200 { + return model.NewAppError("testSiteURL", "app.admin.test_site_url.failure", nil, "", http.StatusBadRequest) + } + + return nil +} + func (a *App) TestEmail(userId string, cfg *model.Config) *model.AppError { if len(*cfg.EmailSettings.SMTPServer) == 0 { return model.NewAppError("testEmail", "api.admin.test_email.missing_server", nil, utils.T("api.context.invalid_param.app_error", map[string]interface{}{"Name": "SMTPServer"}), http.StatusBadRequest) diff --git a/i18n/en.json b/i18n/en.json index 71256f8f08..d2e46f64f3 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2774,6 +2774,10 @@ "id": "app.admin.test_email.failure", "translation": "Connection unsuccessful: {{.Error}}" }, + { + "id": "app.admin.test_site_url.failure", + "translation": "This is not a valid live URL" + }, { "id": "app.channel.create_channel.no_team_id.app_error", "translation": "Must specify the team ID to create a channel" diff --git a/model/client4.go b/model/client4.go index 2cd2a69741..a81696597c 100644 --- a/model/client4.go +++ b/model/client4.go @@ -268,6 +268,10 @@ func (c *Client4) GetTestEmailRoute() string { return fmt.Sprintf("/email/test") } +func (c *Client4) GetTestSiteURLRoute() string { + return fmt.Sprintf("/site_url/test") +} + func (c *Client4) GetTestS3Route() string { return fmt.Sprintf("/file/s3_test") } @@ -2912,6 +2916,18 @@ func (c *Client4) TestEmail(config *Config) (bool, *Response) { return CheckStatusOK(r), BuildResponse(r) } +// TestSiteURL will test the validity of a site URL. +func (c *Client4) TestSiteURL(siteURL string) (bool, *Response) { + requestBody := make(map[string]string) + requestBody["site_url"] = siteURL + r, err := c.DoApiPost(c.GetTestSiteURLRoute(), MapToJson(requestBody)) + if err != nil { + return false, BuildErrorResponse(r, err) + } + defer closeBody(r) + return CheckStatusOK(r), BuildResponse(r) +} + // TestS3Connection will attempt to connect to the AWS S3. func (c *Client4) TestS3Connection(config *Config) (bool, *Response) { r, err := c.DoApiPost(c.GetTestS3Route(), config.ToJson()) From 2719c781ed177efcd294f948b68f70f8e5ab4613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Fri, 6 Sep 2019 12:52:51 +0200 Subject: [PATCH 26/47] Convert app/webhooks_test.go t.Fatal calls into assert/require calls (#12039) * Convert app/webhooks_test.go t.Fatal calls into assert/require calls * simplified by repetition * Addressing PR review comments --- app/webhook_test.go | 90 +++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 57 deletions(-) diff --git a/app/webhook_test.go b/app/webhook_test.go index bf9a23a053..d56f45f14a 100644 --- a/app/webhook_test.go +++ b/app/webhook_test.go @@ -120,8 +120,6 @@ func TestCreateIncomingWebhookForChannel(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - assert := assert.New(t) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostUsernameOverride = tc.EnablePostUsernameOverride @@ -129,22 +127,22 @@ func TestCreateIncomingWebhookForChannel(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride }) createdHook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &tc.IncomingWebhook) - if tc.ExpectedError && err == nil { - t.Fatal("should have failed") - } else if !tc.ExpectedError && err != nil { - t.Fatalf("should not have failed: %v", err.Error()) + if tc.ExpectedError { + require.NotNil(t, err, "should have failed") + } else { + require.Nil(t, err, "should not have failed") } if createdHook != nil { defer th.App.DeleteIncomingWebhook(createdHook.Id) } if tc.ExpectedIncomingWebhook == nil { - assert.Nil(createdHook, "expected nil webhook") - } else if assert.NotNil(createdHook, "expected non-nil webhook") { - assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName) - assert.Equal(tc.ExpectedIncomingWebhook.Description, createdHook.Description) - assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId) - assert.Equal(tc.ExpectedIncomingWebhook.Username, createdHook.Username) - assert.Equal(tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL) + assert.Nil(t, createdHook, "expected nil webhook") + } else if assert.NotNil(t, createdHook, "expected non-nil webhook") { + assert.Equal(t, tc.ExpectedIncomingWebhook.DisplayName, createdHook.DisplayName) + assert.Equal(t, tc.ExpectedIncomingWebhook.Description, createdHook.Description) + assert.Equal(t, tc.ExpectedIncomingWebhook.ChannelId, createdHook.ChannelId) + assert.Equal(t, tc.ExpectedIncomingWebhook.Username, createdHook.Username) + assert.Equal(t, tc.ExpectedIncomingWebhook.IconURL, createdHook.IconURL) } }) } @@ -251,16 +249,12 @@ func TestUpdateIncomingWebhook(t *testing.T) { }, } { t.Run(name, func(t *testing.T) { - assert := assert.New(t) - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true }) hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ ChannelId: th.BasicChannel.Id, }) - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) defer th.App.DeleteIncomingWebhook(hook.Id) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = tc.EnableIncomingHooks }) @@ -270,19 +264,19 @@ func TestUpdateIncomingWebhook(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnablePostIconOverride = tc.EnablePostIconOverride }) updatedHook, err := th.App.UpdateIncomingWebhook(hook, &tc.IncomingWebhook) - if tc.ExpectedError && err == nil { - t.Fatal("should have failed") - } else if !tc.ExpectedError && err != nil { - t.Fatalf("should not have failed: %v", err.Error()) + if tc.ExpectedError { + require.NotNil(t, err, "should have failed") + } else { + require.Nil(t, err, "should not have failed") } if tc.ExpectedIncomingWebhook == nil { - assert.Nil(updatedHook, "expected nil webhook") - } else if assert.NotNil(updatedHook, "expected non-nil webhook") { - assert.Equal(tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName) - assert.Equal(tc.ExpectedIncomingWebhook.Description, updatedHook.Description) - assert.Equal(tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId) - assert.Equal(tc.ExpectedIncomingWebhook.Username, updatedHook.Username) - assert.Equal(tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL) + assert.Nil(t, updatedHook, "expected nil webhook") + } else if assert.NotNil(t, updatedHook, "expected non-nil webhook") { + assert.Equal(t, tc.ExpectedIncomingWebhook.DisplayName, updatedHook.DisplayName) + assert.Equal(t, tc.ExpectedIncomingWebhook.Description, updatedHook.Description) + assert.Equal(t, tc.ExpectedIncomingWebhook.ChannelId, updatedHook.ChannelId) + assert.Equal(t, tc.ExpectedIncomingWebhook.Username, updatedHook.Username) + assert.Equal(t, tc.ExpectedIncomingWebhook.IconURL, updatedHook.IconURL) } }) } @@ -295,9 +289,7 @@ func TestCreateWebhookPost(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableIncomingWebhooks = true }) hook, err := th.App.CreateIncomingWebhookForChannel(th.BasicUser.Id, th.BasicChannel, &model.IncomingWebhook{ChannelId: th.BasicChannel.Id}) - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) defer th.App.DeleteIncomingWebhook(hook.Id) post, err := th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", model.StringInterface{ @@ -308,21 +300,14 @@ func TestCreateWebhookPost(t *testing.T) { }, "webhook_display_name": hook.DisplayName, }, model.POST_SLACK_ATTACHMENT, "") - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) - for _, k := range []string{"from_webhook", "attachments", "webhook_display_name"} { - if _, ok := post.Props[k]; !ok { - t.Log("missing one props: " + k) - t.Fatal(k) - } - } + assert.Contains(t, post.Props, "from_webhook", "missing from_webhook prop") + assert.Contains(t, post.Props, "attachments", "missing attachments prop") + assert.Contains(t, post.Props, "webhook_display_name", "missing webhook_display_name prop") _, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, "foo", "user", "http://iconurl", "", nil, model.POST_SYSTEM_GENERIC, "") - if err == nil { - t.Fatal("should have failed - bad post type") - } + require.NotNil(t, err, "Should have failed - bad post type") expectedText := "`<>|<>|`" post, err = th.App.CreateWebhookPost(hook.UserId, th.BasicChannel, expectedText, "user", "http://iconurl", "", model.StringInterface{ @@ -333,9 +318,7 @@ func TestCreateWebhookPost(t *testing.T) { }, "webhook_display_name": hook.DisplayName, }, model.POST_SLACK_ATTACHMENT, "") - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) assert.Equal(t, expectedText, post.Message) expectedText = "< | \n|\n>" @@ -347,9 +330,7 @@ func TestCreateWebhookPost(t *testing.T) { }, "webhook_display_name": hook.DisplayName, }, model.POST_SLACK_ATTACHMENT, "") - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) assert.Equal(t, expectedText, post.Message) expectedText = `commit bc95839e4a430ace453e8b209a3723c000c1729a @@ -377,9 +358,7 @@ Date: Thu Mar 1 19:46:48 2018 +0300 }, "webhook_display_name": hook.DisplayName, }, model.POST_SLACK_ATTACHMENT, "") - if err != nil { - t.Fatal(err.Error()) - } + require.Nil(t, err) assert.Equal(t, expectedText, post.Message) } @@ -495,10 +474,7 @@ func TestCreateOutGoingWebhookWithUsernameAndIconURL(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOutgoingWebhooks = true }) createdHook, err := th.App.CreateOutgoingWebhook(&outgoingWebhook) - - if err != nil { - t.Fatalf("should not have failed: %v", err.Error()) - } + require.Nil(t, err) assert.NotNil(t, createdHook, "should not be null") From 1a9a2b14303654ee7393b04acdcc616be030fea1 Mon Sep 17 00:00:00 2001 From: Miguel de la Cruz Date: Fri, 6 Sep 2019 13:12:32 +0200 Subject: [PATCH 27/47] [MM-17219] Return an empty list instead of null when no teams (#11904) --- api4/team.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api4/team.go b/api4/team.go index 73c3e8d5ba..884e071ec1 100644 --- a/api4/team.go +++ b/api4/team.go @@ -729,7 +729,7 @@ func updateTeamMemberSchemeRoles(c *Context, w http.ResponseWriter, r *http.Requ } func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { - var teams []*model.Team + teams := []*model.Team{} var err *model.AppError var teamsWithCount *model.TeamsWithCount From adb1a98760d4637acb8d6add12aec08b3da2e095 Mon Sep 17 00:00:00 2001 From: Arshdeep Singh Chimni Date: Fri, 6 Sep 2019 16:54:38 +0530 Subject: [PATCH 28/47] Convert tests in "app/plugin_hooks_test.go" to use assert/require (#12061) * change t.Ftal to require * remove t.errorf * fix build error * adjust for custom error * fix TestUserWillLogIn_Blocked * address pr comments --- app/plugin_hooks_test.go | 85 +++++++++++----------------------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index a20a12a0bf..87398ed409 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -11,7 +11,6 @@ import ( "net/http/httptest" "os" "path/filepath" - "strings" "testing" "time" @@ -172,9 +171,8 @@ func TestHookMessageWillBePosted(t *testing.T) { CreateAt: model.GetMillis() - 10000, } post, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) + assert.Equal(t, "message", post.Message) retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id) require.Nil(t, errSingle) @@ -217,15 +215,12 @@ func TestHookMessageWillBePosted(t *testing.T) { CreateAt: model.GetMillis() - 10000, } post, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) + assert.Equal(t, "message_fromplugin", post.Message) - if retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id); err != nil { - t.Fatal(errSingle) - } else { - assert.Equal(t, "message_fromplugin", retrievedPost.Message) - } + retrievedPost, errSingle := th.App.Srv.Store.Post().GetSingle(post.Id) + require.Nil(t, errSingle) + assert.Equal(t, "message_fromplugin", retrievedPost.Message) }) t.Run("multiple updated", func(t *testing.T) { @@ -286,9 +281,7 @@ func TestHookMessageWillBePosted(t *testing.T) { CreateAt: model.GetMillis() - 10000, } post, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) assert.Equal(t, "prefix_message_suffix", post.Message) }) } @@ -332,9 +325,7 @@ func TestHookMessageHasBeenPosted(t *testing.T) { CreateAt: model.GetMillis() - 10000, } _, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) } func TestHookMessageWillBeUpdated(t *testing.T) { @@ -373,15 +364,11 @@ func TestHookMessageWillBeUpdated(t *testing.T) { CreateAt: model.GetMillis() - 10000, } post, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) assert.Equal(t, "message_", post.Message) post.Message = post.Message + "edited_" post, err = th.App.UpdatePost(post, true) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) assert.Equal(t, "message_edited_fromplugin", post.Message) } @@ -425,15 +412,11 @@ func TestHookMessageHasBeenUpdated(t *testing.T) { CreateAt: model.GetMillis() - 10000, } post, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) assert.Equal(t, "message_", post.Message) post.Message = post.Message + "edited" _, err = th.App.UpdatePost(post, true) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) } func TestHookFileWillBeUploaded(t *testing.T) { @@ -582,8 +565,8 @@ func TestHookFileWillBeUploaded(t *testing.T) { assert.Nil(t, err) assert.NotNil(t, response) assert.Equal(t, 1, len(response.FileInfos)) + fileId := response.FileInfos[0].Id - fileInfo, err := th.App.GetFileInfo(fileId) assert.Nil(t, err) assert.NotNil(t, fileInfo) @@ -678,11 +661,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) { defer th.TearDown() err := th.App.UpdatePassword(th.BasicUser, "hunter2") - - if err != nil { - t.Errorf("Error updating user password: %s", err) - } - + assert.Nil(t, err, "Error updating user password: %s", err) tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ ` @@ -711,9 +690,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) { w := httptest.NewRecorder() _, err = th.App.DoLogin(w, r, th.BasicUser, "") - if !strings.HasPrefix(err.Id, "Login rejected by plugin") { - t.Errorf("Expected Login rejected by plugin, got %s", err.Id) - } + assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id) } func TestUserWillLogInIn_Passed(t *testing.T) { @@ -722,9 +699,7 @@ func TestUserWillLogInIn_Passed(t *testing.T) { err := th.App.UpdatePassword(th.BasicUser, "hunter2") - if err != nil { - t.Errorf("Error updating user password: %s", err) - } + assert.Nil(t, err, "Error updating user password: %s", err) tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ @@ -754,13 +729,8 @@ func TestUserWillLogInIn_Passed(t *testing.T) { w := httptest.NewRecorder() session, err := th.App.DoLogin(w, r, th.BasicUser, "") - if err != nil { - t.Errorf("Expected nil, got %s", err) - } - - if session.UserId != th.BasicUser.Id { - t.Errorf("Expected %s, got %s", th.BasicUser.Id, session.UserId) - } + assert.Nil(t, err, "Expected nil, got %s", err) + assert.Equal(t, session.UserId, th.BasicUser.Id) } func TestUserHasLoggedIn(t *testing.T) { @@ -769,9 +739,7 @@ func TestUserHasLoggedIn(t *testing.T) { err := th.App.UpdatePassword(th.BasicUser, "hunter2") - if err != nil { - t.Errorf("Error updating user password: %s", err) - } + assert.Nil(t, err, "Error updating user password: %s", err) tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{ @@ -802,17 +770,13 @@ func TestUserHasLoggedIn(t *testing.T) { w := httptest.NewRecorder() _, err = th.App.DoLogin(w, r, th.BasicUser, "") - if err != nil { - t.Errorf("Expected nil, got %s", err) - } + assert.Nil(t, err, "Expected nil, got %s", err) time.Sleep(2 * time.Second) user, _ := th.App.GetUser(th.BasicUser.Id) - if user.FirstName != "plugin-callback-success" { - t.Errorf("Expected firstname overwrite, got default") - } + assert.Equal(t, user.FirstName, "plugin-callback-success", "Expected firstname overwrite, got default") } func TestUserHasBeenCreated(t *testing.T) { @@ -858,7 +822,6 @@ func TestUserHasBeenCreated(t *testing.T) { user, err = th.App.GetUser(user.Id) require.Nil(t, err) - require.Equal(t, "plugin-callback-success", user.Nickname) } @@ -988,7 +951,5 @@ func TestHookContext(t *testing.T) { CreateAt: model.GetMillis() - 10000, } _, err := th.App.CreatePost(post, th.BasicChannel, false) - if err != nil { - t.Fatal(err) - } + require.Nil(t, err) } From 50e97bd9d39f3b8fc1c573140bfdb6ca4335caff Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 6 Sep 2019 10:55:55 -0400 Subject: [PATCH 29/47] MM-18081 Except bots from team email domain restrictions (#12041) * Except bots from team email domain restrictions * Fix style --- app/team.go | 3 +++ app/team_test.go | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/team.go b/app/team.go index cfb39d7cc4..688f243524 100644 --- a/app/team.go +++ b/app/team.go @@ -89,6 +89,9 @@ func (a *App) isTeamEmailAddressAllowed(email string, allowedDomains string) boo } func (a *App) isTeamEmailAllowed(user *model.User, team *model.Team) bool { + if user.IsBot { + return true + } email := strings.ToLower(user.Email) return a.isTeamEmailAddressAllowed(email, team.AllowedDomains) } diff --git a/app/team_test.go b/app/team_test.go index 88280cdce6..159c0fd61c 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -105,7 +105,7 @@ func TestAddUserToTeam(t *testing.T) { } }) - t.Run("block user by domain", func(t *testing.T) { + t.Run("block user by domain but allow bot", func(t *testing.T) { th.BasicTeam.AllowedDomains = "example.com" if _, err := th.App.UpdateTeam(th.BasicTeam); err != nil { t.Log(err) @@ -131,10 +131,20 @@ func TestAddUserToTeam(t *testing.T) { } defer th.App.PermanentDeleteUser(&user) - if _, err := th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { + if _, err = th.App.AddUserToTeam(th.BasicTeam.Id, ruser.Id, ""); err == nil || err.Where != "JoinUserToTeam" { t.Log(err) t.Fatal("Should not add authservice user") } + + bot, err := th.App.CreateBot(&model.Bot{ + Username: "somebot", + Description: "a bot", + OwnerId: th.BasicUser.Id, + }) + require.Nil(t, err) + + _, err = th.App.AddUserToTeam(th.BasicTeam.Id, bot.UserId, "") + assert.Nil(t, err, "should be able to add bot to domain restricted team") }) t.Run("block user with subdomain", func(t *testing.T) { From 98f7bdcb430a28644e20c83d798c0b714f604c2e Mon Sep 17 00:00:00 2001 From: scott lee davis Date: Fri, 6 Sep 2019 10:08:39 -0700 Subject: [PATCH 30/47] MM-14827 don't kill STDOUT for the server process (#10828) --- Makefile | 4 ++-- cmd/mattermost/commands/server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 510aa0b37c..2dc873582c 100644 --- a/Makefile +++ b/Makefile @@ -361,11 +361,11 @@ ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64") wmic process where "Caption='go.exe' and CommandLine like '%go.exe run%'" call terminate wmic process where "Caption='mattermost.exe' and CommandLine like '%go-build%'" call terminate else - @for PID in $$(ps -ef | grep "[g]o run" | awk '{ print $$2 }'); do \ + @for PID in $$(ps -ef | grep "[g]o run" | grep "disableconfigwatch" | awk '{ print $$2 }'); do \ echo stopping go $$PID; \ kill $$PID; \ done - @for PID in $$(ps -ef | grep "[g]o-build" | awk '{ print $$2 }'); do \ + @for PID in $$(ps -ef | grep "[g]o-build" | grep "disableconfigwatch" | awk '{ print $$2 }'); do \ echo stopping mattermost $$PID; \ kill $$PID; \ done diff --git a/cmd/mattermost/commands/server.go b/cmd/mattermost/commands/server.go index e80313dcb0..b705671f4a 100644 --- a/cmd/mattermost/commands/server.go +++ b/cmd/mattermost/commands/server.go @@ -91,7 +91,7 @@ func runServer(configStore config.Store, disableConfigWatch bool, usedPlatform b // wait for kill signal before attempting to gracefully shutdown // the running service - signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + signal.Notify(interruptChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE) <-interruptChan return nil From 2b18ebe2cf096147c70a0deafdc9dcc215628266 Mon Sep 17 00:00:00 2001 From: Rajat Varyani <37879062+RajatVaryani@users.noreply.github.com> Date: Mon, 9 Sep 2019 17:02:30 +0530 Subject: [PATCH 31/47] [MM-18319] Replace t.Fatal with assert.Equal (#12082) --- app/notification_push_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 69fbb227bc..5efd7228a1 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -881,7 +881,7 @@ func TestGetPushNotificationMessage(t *testing.T) { *cfg.EmailSettings.PushNotificationContents = pushNotificationContents }) - if actualMessage := th.App.getPushNotificationMessage( + actualMessage := th.App.getPushNotificationMessage( tc.Message, tc.explicitMention, tc.channelWideMention, @@ -891,9 +891,9 @@ func TestGetPushNotificationMessage(t *testing.T) { tc.ChannelType, tc.replyToThreadType, utils.GetUserTranslations(locale), - ); actualMessage != tc.ExpectedMessage { - t.Fatalf("Received incorrect push notification message `%v`, expected `%v`", actualMessage, tc.ExpectedMessage) - } + ) + + assert.Equal(t, tc.ExpectedMessage, actualMessage) }) } } From 8a27ec5749f7c27f902939eaf865ebe81ac2ddc7 Mon Sep 17 00:00:00 2001 From: Phillip Ahereza Date: Mon, 9 Sep 2019 14:43:17 +0300 Subject: [PATCH 32/47] [MM-18323] Migrate tests from "app/authorization_test.go" to use testify (#12078) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated test case to use testify * change test from require to assert * Update app/authorization_test.go Co-Authored-By: Jesús Espino --- app/authorization_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/authorization_test.go b/app/authorization_test.go index 0151985629..e8a32ebde6 100644 --- a/app/authorization_test.go +++ b/app/authorization_test.go @@ -4,6 +4,7 @@ package app import ( + "github.com/stretchr/testify/assert" "testing" "github.com/mattermost/mattermost-server/model" @@ -28,10 +29,8 @@ func TestCheckIfRolesGrantPermission(t *testing.T) { {[]string{model.TEAM_ADMIN_ROLE_ID, model.TEAM_USER_ROLE_ID}, model.PERMISSION_MANAGE_SLASH_COMMANDS.Id, true}, } - for testnum, testcase := range cases { - if th.App.RolesGrantPermission(testcase.roles, testcase.permissionId) != testcase.shouldGrant { - t.Fatal("Failed test case ", testnum) - } + for _, testcase := range cases { + assert.Equal(t, th.App.RolesGrantPermission(testcase.roles, testcase.permissionId), testcase.shouldGrant) } } From ec803ada0a1c75d77839cafb4b0468fba50751d8 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Mon, 9 Sep 2019 18:53:33 +0200 Subject: [PATCH 33/47] Converting to structured logging the file jobs/schedulers.go (#12122) * Converting to structured logging the file jobs/schedulers.go * merge mlog warn and error in single error --- jobs/schedulers.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/jobs/schedulers.go b/jobs/schedulers.go index b0cb92fc5a..706e5d7ef4 100644 --- a/jobs/schedulers.go +++ b/jobs/schedulers.go @@ -4,7 +4,6 @@ package jobs import ( - "fmt" "sync" "time" @@ -103,8 +102,7 @@ func (schedulers *Schedulers) Start() *Schedulers { if scheduler != nil { if scheduler.Enabled(cfg) { if _, err := schedulers.scheduleJob(cfg, scheduler); err != nil { - mlog.Warn(fmt.Sprintf("Failed to schedule job with scheduler: %v", scheduler.Name())) - mlog.Error(fmt.Sprint(err)) + mlog.Error("Failed to schedule job", mlog.String("scheduler", scheduler.Name()), mlog.Err(err)) } else { schedulers.setNextRunTime(cfg, idx, now, true) } @@ -148,7 +146,7 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim if !pendingJobs { if pj, err := schedulers.jobs.CheckForPendingJobsByType(scheduler.JobType()); err != nil { - mlog.Error("Failed to set next job run time: " + err.Error()) + mlog.Error("Failed to set next job run time", mlog.Err(err)) schedulers.nextRunTimes[idx] = nil return } else { @@ -158,13 +156,13 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim lastSuccessfulJob, err := schedulers.jobs.GetLastSuccessfulJobByType(scheduler.JobType()) if err != nil { - mlog.Error("Failed to set next job run time: " + err.Error()) + mlog.Error("Failed to set next job run time", mlog.Err(err)) schedulers.nextRunTimes[idx] = nil return } schedulers.nextRunTimes[idx] = scheduler.NextScheduleTime(cfg, now, pendingJobs, lastSuccessfulJob) - mlog.Debug(fmt.Sprintf("Next run time for scheduler %v: %v", scheduler.Name(), schedulers.nextRunTimes[idx])) + mlog.Debug("Next run time for scheduler", mlog.String("scheduler_name", scheduler.Name()), mlog.String("next_runtime", schedulers.nextRunTimes[idx].String())) } func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) { From 45601bb6dcb42ecd7ccaa8d3e779f4e80620519c Mon Sep 17 00:00:00 2001 From: sowmiyamuthuraman <32141844+sowmiyamuthuraman@users.noreply.github.com> Date: Mon, 9 Sep 2019 23:40:17 +0530 Subject: [PATCH 34/47] Fix. Replace t.fatal() calls to testify assert/require calls (#12084) --- app/opengraph_test.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/app/opengraph_test.go b/app/opengraph_test.go index 867f02ea14..cd93fb7bca 100644 --- a/app/opengraph_test.go +++ b/app/opengraph_test.go @@ -4,6 +4,7 @@ package app import ( + "github.com/stretchr/testify/require" "strings" "testing" @@ -108,22 +109,17 @@ func TestMakeOpenGraphURLsAbsolute(t *testing.T) { } { t.Run(name, func(t *testing.T) { og := opengraph.NewOpenGraph() - if err := og.ProcessHTML(strings.NewReader(tc.HTML)); err != nil { - t.Fatal(err) - } + err := og.ProcessHTML(strings.NewReader(tc.HTML)) + require.Nil(t, err) makeOpenGraphURLsAbsolute(og, tc.RequestURL) - if og.URL != tc.URL { - t.Fatalf("incorrect url, expected %v, got %v", tc.URL, og.URL) - } + assert.Equalf(t, og.URL, tc.URL, "incorrect url, expected %v, got %v", tc.URL, og.URL) if len(og.Images) > 0 { - if og.Images[0].URL != tc.ImageURL { - t.Fatalf("incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL) - } - } else if tc.ImageURL != "" { - t.Fatalf("missing image url, expected %v, got nothing", tc.ImageURL) + assert.Equalf(t, og.Images[0].URL, tc.ImageURL, "incorrect image url, expected %v, got %v", tc.ImageURL, og.Images[0].URL) + } else { + assert.Empty(t, tc.ImageURL, "missing image url, expected %v, got nothing", tc.ImageURL) } }) } From 3ea8db5dc6616a61fe4b7a32e8f170f3b676e42f Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Mon, 9 Sep 2019 17:41:29 -0700 Subject: [PATCH 35/47] Fix panic in jobs structured logging. (#12132) --- jobs/schedulers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jobs/schedulers.go b/jobs/schedulers.go index 706e5d7ef4..d415c85e1f 100644 --- a/jobs/schedulers.go +++ b/jobs/schedulers.go @@ -4,6 +4,7 @@ package jobs import ( + "fmt" "sync" "time" @@ -162,7 +163,7 @@ func (schedulers *Schedulers) setNextRunTime(cfg *model.Config, idx int, now tim } schedulers.nextRunTimes[idx] = scheduler.NextScheduleTime(cfg, now, pendingJobs, lastSuccessfulJob) - mlog.Debug("Next run time for scheduler", mlog.String("scheduler_name", scheduler.Name()), mlog.String("next_runtime", schedulers.nextRunTimes[idx].String())) + mlog.Debug("Next run time for scheduler", mlog.String("scheduler_name", scheduler.Name()), mlog.String("next_runtime", fmt.Sprintf("%v", schedulers.nextRunTimes[idx]))) } func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) { From 7ff34395138d6df7be55249931fd1c359641d628 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 08:39:22 +0200 Subject: [PATCH 36/47] Converting to structured logging the file web/handlers.go (#12092) --- web/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/handlers.go b/web/handlers.go index a67c585ec1..9269a04e93 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -57,7 +57,7 @@ type Handler struct { func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { now := time.Now() - mlog.Debug(fmt.Sprintf("%v - %v", r.Method, r.URL.Path)) + mlog.Debug("request:", mlog.String("method", r.Method), mlog.String("url", r.URL.Path)) c := &Context{} c.App = app.New( From f18895667ddb4364721b0048fd9c80070066b970 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 08:40:55 +0200 Subject: [PATCH 37/47] =?UTF-8?q?Converting=20to=20structured=20logging=20?= =?UTF-8?q?the=20file=20store/sqlstore/suppl=E2=80=A6=20(#12090)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- store/sqlstore/supplier_reactions.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/store/sqlstore/supplier_reactions.go b/store/sqlstore/supplier_reactions.go index e607ed94c9..e3822af708 100644 --- a/store/sqlstore/supplier_reactions.go +++ b/store/sqlstore/supplier_reactions.go @@ -4,7 +4,6 @@ package sqlstore import ( - "fmt" "net/http" "github.com/mattermost/gorp" @@ -141,7 +140,7 @@ func (s *SqlReactionStore) DeleteAllWithEmojiName(emojiName string) *model.AppEr for _, reaction := range reactions { if _, err := s.GetMaster().Exec(UPDATE_POST_HAS_REACTIONS_ON_DELETE_QUERY, map[string]interface{}{"PostId": reaction.PostId, "UpdateAt": model.GetMillis()}); err != nil { - mlog.Warn(fmt.Sprintf("Unable to update Post.HasReactions while removing reactions post_id=%v, error=%v", reaction.PostId, err.Error())) + mlog.Warn("Unable to update Post.HasReactions while removing reactions", mlog.String("post_id", reaction.PostId), mlog.Err(err)) } } From d2c3933c8ee0e806766854458739cf1aca2e0bf1 Mon Sep 17 00:00:00 2001 From: Claudio Costa Date: Tue, 10 Sep 2019 09:43:51 +0200 Subject: [PATCH 38/47] [MM-18033] Upgrade minio-go dependency (#12046) * Upgrade minio-go dependency * Update go.mod and go.sum --- api4/apitestlib.go | 4 +- go.mod | 2 +- go.sum | 22 ++- services/filesstore/s3store.go | 6 +- vendor/github.com/minio/minio-go/.travis.yml | 28 --- vendor/github.com/minio/minio-go/Makefile | 15 -- vendor/github.com/minio/minio-go/go.mod | 14 -- vendor/github.com/minio/minio-go/go.sum | 20 -- .../minio/minio-go/{ => v6}/.gitignore | 0 .../github.com/minio/minio-go/v6/.travis.yml | 29 +++ .../minio/minio-go/{ => v6}/CONTRIBUTING.md | 0 .../minio/minio-go/{ => v6}/LICENSE | 0 .../minio/minio-go/{ => v6}/MAINTAINERS.md | 0 vendor/github.com/minio/minio-go/v6/Makefile | 20 ++ .../github.com/minio/minio-go/{ => v6}/NOTICE | 0 .../minio/minio-go/{ => v6}/README.md | 14 +- .../minio/minio-go/{ => v6}/README_zh_CN.md | 10 +- .../minio-go/{ => v6}/api-compose-object.go | 4 +- .../minio/minio-go/{ => v6}/api-datatypes.go | 0 .../minio-go/{ => v6}/api-error-response.go | 2 +- .../minio-go/{ => v6}/api-get-lifecycle.go | 2 +- .../minio-go/{ => v6}/api-get-object-acl.go | 0 .../{ => v6}/api-get-object-context.go | 0 .../minio-go/{ => v6}/api-get-object-file.go | 2 +- .../minio/minio-go/{ => v6}/api-get-object.go | 16 +- .../minio-go/{ => v6}/api-get-options.go | 2 +- .../minio/minio-go/{ => v6}/api-get-policy.go | 2 +- .../minio/minio-go/{ => v6}/api-list.go | 2 +- .../minio-go/{ => v6}/api-notification.go | 30 ++- .../minio/minio-go/{ => v6}/api-presigned.go | 4 +- .../minio/minio-go/{ => v6}/api-put-bucket.go | 2 +- .../{ => v6}/api-put-object-common.go | 6 +- .../{ => v6}/api-put-object-context.go | 0 .../minio-go/{ => v6}/api-put-object-copy.go | 2 +- .../{ => v6}/api-put-object-file-context.go | 2 +- .../minio-go/{ => v6}/api-put-object-file.go | 0 .../{ => v6}/api-put-object-multipart.go | 4 +- .../{ => v6}/api-put-object-streaming.go | 4 +- .../minio/minio-go/{ => v6}/api-put-object.go | 10 +- .../minio/minio-go/{ => v6}/api-remove.go | 2 +- .../minio-go/{ => v6}/api-s3-datatypes.go | 0 .../minio/minio-go/{ => v6}/api-select.go | 4 +- .../minio/minio-go/{ => v6}/api-stat.go | 2 +- .../github.com/minio/minio-go/{ => v6}/api.go | 80 +++++--- .../minio/minio-go/{ => v6}/appveyor.yml | 18 +- .../minio/minio-go/{ => v6}/bucket-cache.go | 20 +- .../minio-go/{ => v6}/bucket-notification.go | 2 +- .../minio/minio-go/{ => v6}/constants.go | 4 +- .../minio/minio-go/{ => v6}/core.go | 75 +++++-- .../minio-go/{ => v6}/functional_tests.go | 186 +++++++++++++++--- vendor/github.com/minio/minio-go/v6/go.mod | 16 ++ vendor/github.com/minio/minio-go/v6/go.sum | 48 +++++ .../minio/minio-go/{ => v6}/hook-reader.go | 0 .../{ => v6}/pkg/credentials/chain.go | 0 .../pkg/credentials/config.json.sample | 2 +- .../{ => v6}/pkg/credentials/credentials.go | 0 .../pkg/credentials/credentials.sample | 0 .../minio-go/{ => v6}/pkg/credentials/doc.go | 0 .../{ => v6}/pkg/credentials/env_aws.go | 0 .../{ => v6}/pkg/credentials/env_minio.go | 0 .../pkg/credentials/file_aws_credentials.go | 0 .../pkg/credentials/file_minio_client.go | 0 .../{ => v6}/pkg/credentials/iam_aws.go | 2 +- .../pkg/credentials/signature-type.go | 0 .../{ => v6}/pkg/credentials/static.go | 0 .../pkg/credentials/sts_client_grants.go | 0 .../pkg/credentials/sts_web_identity.go | 0 .../{ => v6}/pkg/encrypt/server-side.go | 0 .../s3signer/request-signature-streaming.go | 2 +- .../pkg/s3signer/request-signature-v2.go | 2 +- .../pkg/s3signer/request-signature-v4.go | 2 +- .../minio-go/{ => v6}/pkg/s3signer/utils.go | 0 .../minio-go/{ => v6}/pkg/s3utils/utils.go | 6 +- .../minio-go/{ => v6}/pkg/set/stringset.go | 0 .../minio/minio-go/{ => v6}/post-policy.go | 0 .../minio-go/{ => v6}/retry-continous.go | 0 .../minio/minio-go/{ => v6}/retry.go | 4 + .../minio/minio-go/{ => v6}/s3-endpoints.go | 1 + .../minio/minio-go/{ => v6}/s3-error.go | 0 .../minio/minio-go/v6/staticcheck.conf | 1 + .../minio/minio-go/{ => v6}/transport.go | 0 .../minio/minio-go/{ => v6}/utils.go | 4 +- vendor/modules.txt | 14 +- 83 files changed, 515 insertions(+), 262 deletions(-) delete mode 100644 vendor/github.com/minio/minio-go/.travis.yml delete mode 100644 vendor/github.com/minio/minio-go/Makefile delete mode 100644 vendor/github.com/minio/minio-go/go.mod delete mode 100644 vendor/github.com/minio/minio-go/go.sum rename vendor/github.com/minio/minio-go/{ => v6}/.gitignore (100%) create mode 100644 vendor/github.com/minio/minio-go/v6/.travis.yml rename vendor/github.com/minio/minio-go/{ => v6}/CONTRIBUTING.md (100%) rename vendor/github.com/minio/minio-go/{ => v6}/LICENSE (100%) rename vendor/github.com/minio/minio-go/{ => v6}/MAINTAINERS.md (100%) create mode 100644 vendor/github.com/minio/minio-go/v6/Makefile rename vendor/github.com/minio/minio-go/{ => v6}/NOTICE (100%) rename vendor/github.com/minio/minio-go/{ => v6}/README.md (96%) rename vendor/github.com/minio/minio-go/{ => v6}/README_zh_CN.md (97%) rename vendor/github.com/minio/minio-go/{ => v6}/api-compose-object.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-datatypes.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-error-response.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-lifecycle.go (97%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-object-acl.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-object-context.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-object-file.go (98%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-object.go (98%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-options.go (98%) rename vendor/github.com/minio/minio-go/{ => v6}/api-get-policy.go (97%) rename vendor/github.com/minio/minio-go/{ => v6}/api-list.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-notification.go (93%) rename vendor/github.com/minio/minio-go/{ => v6}/api-presigned.go (98%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-bucket.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-common.go (96%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-context.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-copy.go (97%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-file-context.go (97%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-file.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-multipart.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object-streaming.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-put-object.go (96%) rename vendor/github.com/minio/minio-go/{ => v6}/api-remove.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-s3-datatypes.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/api-select.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api-stat.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/api.go (92%) rename vendor/github.com/minio/minio-go/{ => v6}/appveyor.yml (58%) rename vendor/github.com/minio/minio-go/{ => v6}/bucket-cache.go (93%) rename vendor/github.com/minio/minio-go/{ => v6}/bucket-notification.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/constants.go (95%) rename vendor/github.com/minio/minio-go/{ => v6}/core.go (61%) rename vendor/github.com/minio/minio-go/{ => v6}/functional_tests.go (98%) create mode 100644 vendor/github.com/minio/minio-go/v6/go.mod create mode 100644 vendor/github.com/minio/minio-go/v6/go.sum rename vendor/github.com/minio/minio-go/{ => v6}/hook-reader.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/chain.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/config.json.sample (88%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/credentials.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/credentials.sample (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/doc.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/env_aws.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/env_minio.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/file_aws_credentials.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/file_minio_client.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/iam_aws.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/signature-type.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/static.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/sts_client_grants.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/credentials/sts_web_identity.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/encrypt/server-side.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/s3signer/request-signature-streaming.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/s3signer/request-signature-v2.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/s3signer/request-signature-v4.go (99%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/s3signer/utils.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/s3utils/utils.go (98%) rename vendor/github.com/minio/minio-go/{ => v6}/pkg/set/stringset.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/post-policy.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/retry-continous.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/retry.go (95%) rename vendor/github.com/minio/minio-go/{ => v6}/s3-endpoints.go (97%) rename vendor/github.com/minio/minio-go/{ => v6}/s3-error.go (100%) create mode 100644 vendor/github.com/minio/minio-go/v6/staticcheck.conf rename vendor/github.com/minio/minio-go/{ => v6}/transport.go (100%) rename vendor/github.com/minio/minio-go/{ => v6}/utils.go (98%) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index f70bdbee3b..32aa279c67 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -25,8 +25,8 @@ import ( "github.com/mattermost/mattermost-server/web" "github.com/mattermost/mattermost-server/wsapi" - s3 "github.com/minio/minio-go" - "github.com/minio/minio-go/pkg/credentials" + s3 "github.com/minio/minio-go/v6" + "github.com/minio/minio-go/v6/pkg/credentials" ) type TestHelper struct { diff --git a/go.mod b/go.mod index a51e2772ca..be78cdd841 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-sqlite3 v1.11.0 github.com/miekg/dns v1.1.15 // indirect - github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f + github.com/minio/minio-go/v6 v6.0.34 github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/muesli/smartcrop v0.3.0 // indirect github.com/olekukonko/tablewriter v0.0.1 // indirect diff --git a/go.sum b/go.sum index 13c95d32a8..d99bd9a686 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845/go.mod h1:c8Mh99Cw82nrsAnPgxQSZHkswVOJF7/MqZb1ZdvriLM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= @@ -65,6 +66,7 @@ github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjC github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA= github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -79,6 +81,7 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/gernest/wow v0.1.0/go.mod h1:dEPabJRi5BneI1Nev1VWo0ZlcTWibHWp43qxKms4elY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= @@ -127,7 +130,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -202,7 +204,6 @@ github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -254,8 +255,9 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI= github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f h1:pIUObqY9ljwlPfUINTvfWhEuoJyy9dkCloS2f5Mf1Ks= -github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f/go.mod h1:/haSOWG8hQNx2+JOfLJ9GKp61EAmgPwRVw/Sac0NzaM= +github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY= +github.com/minio/minio-go/v6 v6.0.34 h1:ESPDlIg8Pe2BRvsxPomd0xB72uLmsXrkDNoze36yb90= +github.com/minio/minio-go/v6 v6.0.34/go.mod h1:vaNT59cWULS37E+E9zkuN/BVnKHyXtVGS+b04Boc66Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -355,11 +357,10 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -413,9 +414,10 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -446,6 +448,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= @@ -475,7 +478,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -545,7 +548,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0= gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= @@ -557,6 +560,7 @@ gopkg.in/olivere/elastic.v5 v5.0.81/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0F gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/services/filesstore/s3store.go b/services/filesstore/s3store.go index 42113c1b43..2e80aab8b2 100644 --- a/services/filesstore/s3store.go +++ b/services/filesstore/s3store.go @@ -12,9 +12,9 @@ import ( "path/filepath" "strings" - s3 "github.com/minio/minio-go" - "github.com/minio/minio-go/pkg/credentials" - "github.com/minio/minio-go/pkg/encrypt" + s3 "github.com/minio/minio-go/v6" + "github.com/minio/minio-go/v6/pkg/credentials" + "github.com/minio/minio-go/v6/pkg/encrypt" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" diff --git a/vendor/github.com/minio/minio-go/.travis.yml b/vendor/github.com/minio/minio-go/.travis.yml deleted file mode 100644 index 7ed7df14e7..0000000000 --- a/vendor/github.com/minio/minio-go/.travis.yml +++ /dev/null @@ -1,28 +0,0 @@ -sudo: false -language: go - -os: -- linux - -env: -- ARCH=x86_64 -- ARCH=i686 - -go: -- 1.11.x -- tip - -matrix: - fast_finish: true - allow_failures: - - go: tip - -addons: - apt: - packages: - - devscripts - -script: -- diff -au <(gofmt -d .) <(printf "") -- diff -au <(licensecheck --check '.go$' --recursive --lines 0 * | grep -v -w 'Apache (v2.0)') <(printf "") -- make diff --git a/vendor/github.com/minio/minio-go/Makefile b/vendor/github.com/minio/minio-go/Makefile deleted file mode 100644 index 51c8ca266e..0000000000 --- a/vendor/github.com/minio/minio-go/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -all: checks - -checks: - @go get -t ./... - @go vet ./... - @SERVER_ENDPOINT=play.min.io:9000 ACCESS_KEY=Q3AM3UQ867SPQQA43P2F SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG ENABLE_HTTPS=1 MINT_MODE=full go test -race -v ./... - @go get github.com/dustin/go-humanize/... - @go get github.com/sirupsen/logrus/... - @SERVER_ENDPOINT=play.min.io:9000 ACCESS_KEY=Q3AM3UQ867SPQQA43P2F SECRET_KEY=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG ENABLE_HTTPS=1 MINT_MODE=full go run functional_tests.go - @mkdir -p /tmp/examples && for i in $(echo examples/s3/*); do go build -o /tmp/examples/$(basename ${i:0:-3}) ${i}; done - @go get -u github.com/a8m/mark/... - @go get -u github.com/minio/cli/... - @go get -u golang.org/x/tools/cmd/goimports - @go get -u github.com/gernest/wow/... - @go build docs/validator.go && ./validator -m docs/API.md -t docs/checker.go.tpl diff --git a/vendor/github.com/minio/minio-go/go.mod b/vendor/github.com/minio/minio-go/go.mod deleted file mode 100644 index 59ef0a7943..0000000000 --- a/vendor/github.com/minio/minio-go/go.mod +++ /dev/null @@ -1,14 +0,0 @@ -module github.com/minio/minio-go - -require ( - github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect - github.com/jtolds/gls v4.2.1+incompatible // indirect - github.com/mitchellh/go-homedir v1.1.0 - github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect - github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect - golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b - golang.org/x/net v0.0.0-20190213061140-3a22650c66bd - golang.org/x/sys v0.0.0-20190124100055-b90733256f2e // indirect - golang.org/x/text v0.3.0 // indirect - gopkg.in/ini.v1 v1.41.0 -) diff --git a/vendor/github.com/minio/minio-go/go.sum b/vendor/github.com/minio/minio-go/go.sum deleted file mode 100644 index baf2f44e84..0000000000 --- a/vendor/github.com/minio/minio-go/go.sum +++ /dev/null @@ -1,20 +0,0 @@ -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= -golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b h1:Ib/yptP38nXZFMwqWSip+OKuMP9OkyDe3p+DssP8n9w= -golang.org/x/crypto v0.0.0-20190128193316-c7b33c32a30b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd h1:HuTn7WObtcDo9uEEU7rEqL0jYthdXAmZ6PP+meazmaU= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e h1:3GIlrlVLfkoipSReOMNAgApI0ajnalyLa/EZHHca/XI= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/ini.v1 v1.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE= -gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/vendor/github.com/minio/minio-go/.gitignore b/vendor/github.com/minio/minio-go/v6/.gitignore similarity index 100% rename from vendor/github.com/minio/minio-go/.gitignore rename to vendor/github.com/minio/minio-go/v6/.gitignore diff --git a/vendor/github.com/minio/minio-go/v6/.travis.yml b/vendor/github.com/minio/minio-go/v6/.travis.yml new file mode 100644 index 0000000000..5dbda5e7de --- /dev/null +++ b/vendor/github.com/minio/minio-go/v6/.travis.yml @@ -0,0 +1,29 @@ +sudo: false +language: go + +os: +- linux + +env: +- ARCH=x86_64 + +go: +- 1.12.x +- tip + +matrix: + fast_finish: true + allow_failures: + - go: tip + +before_install: + - sudo apt-get install devscripts + - curl -O https://dl.minio.io/server/minio/release/linux-amd64/minio && chmod +x ./minio + - sudo cp testcerts/public.crt /usr/local/share/ca-certificates/ + - sudo update-ca-certificates + - MINIO_ACCESS_KEY=minio MINIO_SECRET_KEY=minio123 ./minio server --compat --quiet --certs-dir testcerts data 2>&1 > minio.log & + +script: + - diff -au <(gofmt -d .) <(printf "") + - diff -au <(licensecheck --check '.go$' --recursive --lines 0 * | grep -v -w 'Apache (v2.0)') <(printf "") + - make diff --git a/vendor/github.com/minio/minio-go/CONTRIBUTING.md b/vendor/github.com/minio/minio-go/v6/CONTRIBUTING.md similarity index 100% rename from vendor/github.com/minio/minio-go/CONTRIBUTING.md rename to vendor/github.com/minio/minio-go/v6/CONTRIBUTING.md diff --git a/vendor/github.com/minio/minio-go/LICENSE b/vendor/github.com/minio/minio-go/v6/LICENSE similarity index 100% rename from vendor/github.com/minio/minio-go/LICENSE rename to vendor/github.com/minio/minio-go/v6/LICENSE diff --git a/vendor/github.com/minio/minio-go/MAINTAINERS.md b/vendor/github.com/minio/minio-go/v6/MAINTAINERS.md similarity index 100% rename from vendor/github.com/minio/minio-go/MAINTAINERS.md rename to vendor/github.com/minio/minio-go/v6/MAINTAINERS.md diff --git a/vendor/github.com/minio/minio-go/v6/Makefile b/vendor/github.com/minio/minio-go/v6/Makefile new file mode 100644 index 0000000000..892f8cf507 --- /dev/null +++ b/vendor/github.com/minio/minio-go/v6/Makefile @@ -0,0 +1,20 @@ +all: checks + +.PHONY: examples docs + +checks: vet test examples docs functional-test + +vet: + @GO111MODULE=on go vet ./... + +test: + @GO111MODULE=on SERVER_ENDPOINT=localhost:9000 ACCESS_KEY=minio SECRET_KEY=minio123 ENABLE_HTTPS=1 MINT_MODE=full go test -race -v ./... + +examples: + @mkdir -p /tmp/examples && for i in $(echo examples/s3/*); do go build -o /tmp/examples/$(basename ${i:0:-3}) ${i}; done + +docs: + @(cd docs; GO111MODULE=on go build validator.go && ./validator -m ../docs/API.md -t checker.go.tpl) + +functional-test: + @GO111MODULE=on SERVER_ENDPOINT=localhost:9000 ACCESS_KEY=minio SECRET_KEY=minio123 ENABLE_HTTPS=1 MINT_MODE=full go run functional_tests.go diff --git a/vendor/github.com/minio/minio-go/NOTICE b/vendor/github.com/minio/minio-go/v6/NOTICE similarity index 100% rename from vendor/github.com/minio/minio-go/NOTICE rename to vendor/github.com/minio/minio-go/v6/NOTICE diff --git a/vendor/github.com/minio/minio-go/README.md b/vendor/github.com/minio/minio-go/v6/README.md similarity index 96% rename from vendor/github.com/minio/minio-go/README.md rename to vendor/github.com/minio/minio-go/v6/README.md index 5464390fa1..81320a14fb 100644 --- a/vendor/github.com/minio/minio-go/README.md +++ b/vendor/github.com/minio/minio-go/v6/README.md @@ -26,12 +26,12 @@ MinIO client requires the following four parameters specified to connect to an A package main import ( - "github.com/minio/minio-go" + "github.com/minio/minio-go/v6" "log" ) func main() { - endpoint := "play.min.io:9000" + endpoint := "play.min.io" accessKeyID := "Q3AM3UQ867SPQQA43P2F" secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" useSSL := true @@ -49,19 +49,19 @@ func main() { ## Quick Start Example - File Uploader This example program connects to an object storage server, creates a bucket and uploads a file to the bucket. -We will use the MinIO server running at [https://play.min.io:9000](https://play.min.io:9000) in this example. Feel free to use this service for testing and development. Access credentials shown in this example are open to the public. +We will use the MinIO server running at [https://play.min.io](https://play.min.io) in this example. Feel free to use this service for testing and development. Access credentials shown in this example are open to the public. ### FileUploader.go ```go package main import ( - "github.com/minio/minio-go" + "github.com/minio/minio-go/v6" "log" ) func main() { - endpoint := "play.min.io:9000" + endpoint := "play.min.io" accessKeyID := "Q3AM3UQ867SPQQA43P2F" secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" useSSL := true @@ -79,8 +79,8 @@ func main() { err = minioClient.MakeBucket(bucketName, location) if err != nil { // Check to see if we already own this bucket (which happens if you run this twice) - exists, err := minioClient.BucketExists(bucketName) - if err == nil && exists { + exists, errBucketExists := minioClient.BucketExists(bucketName) + if errBucketExists == nil && exists { log.Printf("We already own %s\n", bucketName) } else { log.Fatalln(err) diff --git a/vendor/github.com/minio/minio-go/README_zh_CN.md b/vendor/github.com/minio/minio-go/v6/README_zh_CN.md similarity index 97% rename from vendor/github.com/minio/minio-go/README_zh_CN.md rename to vendor/github.com/minio/minio-go/v6/README_zh_CN.md index 061f41743d..bc0c45c0fe 100644 --- a/vendor/github.com/minio/minio-go/README_zh_CN.md +++ b/vendor/github.com/minio/minio-go/v6/README_zh_CN.md @@ -38,12 +38,12 @@ MinIO client需要以下4个参数来连接与Amazon S3兼容的对象存储。 package main import ( - "github.com/minio/minio-go" + "github.com/minio/minio-go/v6" "log" ) func main() { - endpoint := "play.min.io:9000" + endpoint := "play.min.io" accessKeyID := "Q3AM3UQ867SPQQA43P2F" secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" useSSL := true @@ -61,19 +61,19 @@ func main() { ## 示例-文件上传 本示例连接到一个对象存储服务,创建一个存储桶并上传一个文件到存储桶中。 -我们在本示例中使用运行在 [https://play.min.io:9000](https://play.min.io:9000) 上的MinIO服务,你可以用这个服务来开发和测试。示例中的访问凭据是公开的。 +我们在本示例中使用运行在 [https://play.min.io](https://play.min.io) 上的MinIO服务,你可以用这个服务来开发和测试。示例中的访问凭据是公开的。 ### FileUploader.go ```go package main import ( - "github.com/minio/minio-go" + "github.com/minio/minio-go/v6" "log" ) func main() { - endpoint := "play.min.io:9000" + endpoint := "play.min.io" accessKeyID := "Q3AM3UQ867SPQQA43P2F" secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" useSSL := true diff --git a/vendor/github.com/minio/minio-go/api-compose-object.go b/vendor/github.com/minio/minio-go/v6/api-compose-object.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-compose-object.go rename to vendor/github.com/minio/minio-go/v6/api-compose-object.go index b4dddacb2d..748b558ce3 100644 --- a/vendor/github.com/minio/minio-go/api-compose-object.go +++ b/vendor/github.com/minio/minio-go/v6/api-compose-object.go @@ -28,8 +28,8 @@ import ( "strings" "time" - "github.com/minio/minio-go/pkg/encrypt" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // DestinationInfo - type with information about the object to be diff --git a/vendor/github.com/minio/minio-go/api-datatypes.go b/vendor/github.com/minio/minio-go/v6/api-datatypes.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-datatypes.go rename to vendor/github.com/minio/minio-go/v6/api-datatypes.go diff --git a/vendor/github.com/minio/minio-go/api-error-response.go b/vendor/github.com/minio/minio-go/v6/api-error-response.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-error-response.go rename to vendor/github.com/minio/minio-go/v6/api-error-response.go index 5dc8a0a90b..726fdd274a 100644 --- a/vendor/github.com/minio/minio-go/api-error-response.go +++ b/vendor/github.com/minio/minio-go/v6/api-error-response.go @@ -60,7 +60,7 @@ type ErrorResponse struct { // // For example: // -// import s3 "github.com/minio/minio-go" +// import s3 "github.com/minio/minio-go/v6" // ... // ... // reader, stat, err := s3.GetObject(...) diff --git a/vendor/github.com/minio/minio-go/api-get-lifecycle.go b/vendor/github.com/minio/minio-go/v6/api-get-lifecycle.go similarity index 97% rename from vendor/github.com/minio/minio-go/api-get-lifecycle.go rename to vendor/github.com/minio/minio-go/v6/api-get-lifecycle.go index 96ccfe9029..a24d03e370 100644 --- a/vendor/github.com/minio/minio-go/api-get-lifecycle.go +++ b/vendor/github.com/minio/minio-go/v6/api-get-lifecycle.go @@ -23,7 +23,7 @@ import ( "net/http" "net/url" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // GetBucketLifecycle - get bucket lifecycle. diff --git a/vendor/github.com/minio/minio-go/api-get-object-acl.go b/vendor/github.com/minio/minio-go/v6/api-get-object-acl.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-get-object-acl.go rename to vendor/github.com/minio/minio-go/v6/api-get-object-acl.go diff --git a/vendor/github.com/minio/minio-go/api-get-object-context.go b/vendor/github.com/minio/minio-go/v6/api-get-object-context.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-get-object-context.go rename to vendor/github.com/minio/minio-go/v6/api-get-object-context.go diff --git a/vendor/github.com/minio/minio-go/api-get-object-file.go b/vendor/github.com/minio/minio-go/v6/api-get-object-file.go similarity index 98% rename from vendor/github.com/minio/minio-go/api-get-object-file.go rename to vendor/github.com/minio/minio-go/v6/api-get-object-file.go index 98837bfa3e..9c82a7c4f9 100644 --- a/vendor/github.com/minio/minio-go/api-get-object-file.go +++ b/vendor/github.com/minio/minio-go/v6/api-get-object-file.go @@ -23,7 +23,7 @@ import ( "os" "path/filepath" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // FGetObjectWithContext - download contents of an object to a local file. diff --git a/vendor/github.com/minio/minio-go/api-get-object.go b/vendor/github.com/minio/minio-go/v6/api-get-object.go similarity index 98% rename from vendor/github.com/minio/minio-go/api-get-object.go rename to vendor/github.com/minio/minio-go/v6/api-get-object.go index a00ddaa895..5d03ea9bb7 100644 --- a/vendor/github.com/minio/minio-go/api-get-object.go +++ b/vendor/github.com/minio/minio-go/v6/api-get-object.go @@ -27,7 +27,7 @@ import ( "sync" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // GetObject - returns an seekable, readable object. @@ -321,6 +321,7 @@ func (o *Object) Read(b []byte) (n int, err error) { if o.prevErr != nil || o.isClosed { return 0, o.prevErr } + // Create a new request. readReq := getRequest{ isReadOp: true, @@ -403,10 +404,13 @@ func (o *Object) ReadAt(b []byte, offset int64) (n int, err error) { defer o.mutex.Unlock() // prevErr is error which was saved in previous operation. - if o.prevErr != nil || o.isClosed { + if o.prevErr != nil && o.prevErr != io.EOF || o.isClosed { return 0, o.prevErr } + // Set the current offset to ReadAt offset, because the current offset will be shifted at the end of this method. + o.currOffset = offset + // Can only compare offsets to size when size has been set. if o.objectInfoSet { // If offset is negative than we return io.EOF. @@ -476,11 +480,9 @@ func (o *Object) Seek(offset int64, whence int) (n int64, err error) { o.mutex.Lock() defer o.mutex.Unlock() - if o.prevErr != nil { - // At EOF seeking is legal allow only io.EOF, for any other errors we return. - if o.prevErr != io.EOF { - return 0, o.prevErr - } + // At EOF seeking is legal allow only io.EOF, for any other errors we return. + if o.prevErr != nil && o.prevErr != io.EOF { + return 0, o.prevErr } // Negative offset is valid for whence of '2'. diff --git a/vendor/github.com/minio/minio-go/api-get-options.go b/vendor/github.com/minio/minio-go/v6/api-get-options.go similarity index 98% rename from vendor/github.com/minio/minio-go/api-get-options.go rename to vendor/github.com/minio/minio-go/v6/api-get-options.go index 323b1c909a..538fd1a052 100644 --- a/vendor/github.com/minio/minio-go/api-get-options.go +++ b/vendor/github.com/minio/minio-go/v6/api-get-options.go @@ -22,7 +22,7 @@ import ( "net/http" "time" - "github.com/minio/minio-go/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/encrypt" ) // GetObjectOptions are used to specify additional headers or options diff --git a/vendor/github.com/minio/minio-go/api-get-policy.go b/vendor/github.com/minio/minio-go/v6/api-get-policy.go similarity index 97% rename from vendor/github.com/minio/minio-go/api-get-policy.go rename to vendor/github.com/minio/minio-go/v6/api-get-policy.go index 035685d044..bc1d10530a 100644 --- a/vendor/github.com/minio/minio-go/api-get-policy.go +++ b/vendor/github.com/minio/minio-go/v6/api-get-policy.go @@ -23,7 +23,7 @@ import ( "net/http" "net/url" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // GetBucketPolicy - get bucket policy at a given path. diff --git a/vendor/github.com/minio/minio-go/api-list.go b/vendor/github.com/minio/minio-go/v6/api-list.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-list.go rename to vendor/github.com/minio/minio-go/v6/api-list.go index 6c4259eb41..b9c0f5d8d0 100644 --- a/vendor/github.com/minio/minio-go/api-list.go +++ b/vendor/github.com/minio/minio-go/v6/api-list.go @@ -25,7 +25,7 @@ import ( "net/url" "strings" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // ListBuckets list all buckets owned by this authenticated user. diff --git a/vendor/github.com/minio/minio-go/api-notification.go b/vendor/github.com/minio/minio-go/v6/api-notification.go similarity index 93% rename from vendor/github.com/minio/minio-go/api-notification.go rename to vendor/github.com/minio/minio-go/v6/api-notification.go index 38a3f992ff..f35619541e 100644 --- a/vendor/github.com/minio/minio-go/api-notification.go +++ b/vendor/github.com/minio/minio-go/v6/api-notification.go @@ -21,12 +21,11 @@ import ( "bufio" "context" "encoding/json" - "io" "net/http" "net/url" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // GetBucketNotification - get bucket notification at a given path. @@ -164,13 +163,14 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even // Indicate to our routine to exit cleanly upon return. defer close(retryDoneCh) + // Prepare urlValues to pass into the request on every loop + urlValues := make(url.Values) + urlValues.Set("prefix", prefix) + urlValues.Set("suffix", suffix) + urlValues["events"] = events + // Wait on the jitter retry loop. for range c.newRetryTimerContinous(time.Second, time.Second*30, MaxJitter, retryDoneCh) { - urlValues := make(url.Values) - urlValues.Set("prefix", prefix) - urlValues.Set("suffix", suffix) - urlValues["events"] = events - // Execute GET on bucket to list objects. resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{ bucketName: bucketName, @@ -196,30 +196,24 @@ func (c Client) ListenBucketNotification(bucketName, prefix, suffix string, even // Initialize a new bufio scanner, to read line by line. bio := bufio.NewScanner(resp.Body) - // Close the response body. - defer resp.Body.Close() - // Unmarshal each line, returns marshalled values. for bio.Scan() { var notificationInfo NotificationInfo if err = json.Unmarshal(bio.Bytes(), ¬ificationInfo); err != nil { + closeResponse(resp) continue } // Send notificationInfo select { case notificationInfoCh <- notificationInfo: case <-doneCh: + closeResponse(resp) return } } - // Look for any underlying errors. - if err = bio.Err(); err != nil { - // For an unexpected connection drop from server, we close the body - // and re-connect. - if err == io.ErrUnexpectedEOF { - resp.Body.Close() - } - } + + // Close current connection before looping further. + closeResponse(resp) } }(notificationInfoCh) diff --git a/vendor/github.com/minio/minio-go/api-presigned.go b/vendor/github.com/minio/minio-go/v6/api-presigned.go similarity index 98% rename from vendor/github.com/minio/minio-go/api-presigned.go rename to vendor/github.com/minio/minio-go/v6/api-presigned.go index 8ffcdd712e..e2d68b0ece 100644 --- a/vendor/github.com/minio/minio-go/api-presigned.go +++ b/vendor/github.com/minio/minio-go/v6/api-presigned.go @@ -23,8 +23,8 @@ import ( "net/url" "time" - "github.com/minio/minio-go/pkg/s3signer" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3signer" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // presignURL - Returns a presigned URL for an input 'method'. diff --git a/vendor/github.com/minio/minio-go/api-put-bucket.go b/vendor/github.com/minio/minio-go/v6/api-put-bucket.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-put-bucket.go rename to vendor/github.com/minio/minio-go/v6/api-put-bucket.go index 69d33e3151..28613a2291 100644 --- a/vendor/github.com/minio/minio-go/api-put-bucket.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-bucket.go @@ -26,7 +26,7 @@ import ( "net/url" "strings" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) /// Bucket operations diff --git a/vendor/github.com/minio/minio-go/api-put-object-common.go b/vendor/github.com/minio/minio-go/v6/api-put-object-common.go similarity index 96% rename from vendor/github.com/minio/minio-go/api-put-object-common.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-common.go index 00d98b2902..a786d2a8e0 100644 --- a/vendor/github.com/minio/minio-go/api-put-object-common.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object-common.go @@ -23,7 +23,7 @@ import ( "math" "os" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // Verify if reader is *minio.Object @@ -64,7 +64,7 @@ func isReadAt(reader io.Reader) (ok bool) { // object storage it will have the following parameters as constants. // // maxPartsCount - 10000 -// minPartSize - 64MiB +// minPartSize - 128MiB // maxMultipartPutObjectSize - 5TiB // func optimalPartInfo(objectSize int64, configuredPartSize uint64) (totalPartsCount int, partSize int64, lastPartSize int64, err error) { @@ -105,7 +105,7 @@ func optimalPartInfo(objectSize int64, configuredPartSize uint64) (totalPartsCou configuredPartSize = minPartSize // Use floats for part size for all calculations to avoid // overflows during float64 to int64 conversions. - partSizeFlt = math.Ceil(float64(objectSize / maxPartsCount)) + partSizeFlt = float64(objectSize / maxPartsCount) partSizeFlt = math.Ceil(partSizeFlt/float64(configuredPartSize)) * float64(configuredPartSize) } diff --git a/vendor/github.com/minio/minio-go/api-put-object-context.go b/vendor/github.com/minio/minio-go/v6/api-put-object-context.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-put-object-context.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-context.go diff --git a/vendor/github.com/minio/minio-go/api-put-object-copy.go b/vendor/github.com/minio/minio-go/v6/api-put-object-copy.go similarity index 97% rename from vendor/github.com/minio/minio-go/api-put-object-copy.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-copy.go index c6ecf39d85..19e58add89 100644 --- a/vendor/github.com/minio/minio-go/api-put-object-copy.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object-copy.go @@ -23,7 +23,7 @@ import ( "io/ioutil" "net/http" - "github.com/minio/minio-go/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/encrypt" ) // CopyObject - copy a source object into a new object diff --git a/vendor/github.com/minio/minio-go/api-put-object-file-context.go b/vendor/github.com/minio/minio-go/v6/api-put-object-file-context.go similarity index 97% rename from vendor/github.com/minio/minio-go/api-put-object-file-context.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-file-context.go index 3574a6b5b4..fb22c0d64b 100644 --- a/vendor/github.com/minio/minio-go/api-put-object-file-context.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object-file-context.go @@ -23,7 +23,7 @@ import ( "os" "path/filepath" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // FPutObjectWithContext - Create an object in a bucket, with contents from file at filePath. Allows request cancellation. diff --git a/vendor/github.com/minio/minio-go/api-put-object-file.go b/vendor/github.com/minio/minio-go/v6/api-put-object-file.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-put-object-file.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-file.go diff --git a/vendor/github.com/minio/minio-go/api-put-object-multipart.go b/vendor/github.com/minio/minio-go/v6/api-put-object-multipart.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-put-object-multipart.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-multipart.go index 95a2599b0f..ab284f9869 100644 --- a/vendor/github.com/minio/minio-go/api-put-object-multipart.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object-multipart.go @@ -33,8 +33,8 @@ import ( "strconv" "strings" - "github.com/minio/minio-go/pkg/encrypt" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/s3utils" ) func (c Client) putObjectMultipart(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64, diff --git a/vendor/github.com/minio/minio-go/api-put-object-streaming.go b/vendor/github.com/minio/minio-go/v6/api-put-object-streaming.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-put-object-streaming.go rename to vendor/github.com/minio/minio-go/v6/api-put-object-streaming.go index 0035def3e7..0d3f2455a7 100644 --- a/vendor/github.com/minio/minio-go/api-put-object-streaming.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object-streaming.go @@ -25,7 +25,7 @@ import ( "sort" "strings" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // putObjectMultipartStream - upload a large object using @@ -75,7 +75,7 @@ type uploadPartReq struct { Part *ObjectPart // Size of the part uploaded. } -// putObjectMultipartFromReadAt - Uploads files bigger than 64MiB. +// putObjectMultipartFromReadAt - Uploads files bigger than 128MiB. // Supports all readers which implements io.ReaderAt interface // (ReadAt method). // diff --git a/vendor/github.com/minio/minio-go/api-put-object.go b/vendor/github.com/minio/minio-go/v6/api-put-object.go similarity index 96% rename from vendor/github.com/minio/minio-go/api-put-object.go rename to vendor/github.com/minio/minio-go/v6/api-put-object.go index 4ea9c495a5..8f33fca6cb 100644 --- a/vendor/github.com/minio/minio-go/api-put-object.go +++ b/vendor/github.com/minio/minio-go/v6/api-put-object.go @@ -26,8 +26,8 @@ import ( "runtime/debug" "sort" - "github.com/minio/minio-go/pkg/encrypt" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/s3utils" "golang.org/x/net/http/httpguts" ) @@ -124,9 +124,9 @@ func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].Part // // You must have WRITE permissions on a bucket to create an object. // -// - For size smaller than 64MiB PutObject automatically does a +// - For size smaller than 128MiB PutObject automatically does a // single atomic Put operation. -// - For size larger than 64MiB PutObject automatically does a +// - For size larger than 128MiB PutObject automatically does a // multipart Put operation. // - For size input as -1 PutObject does a multipart Put operation // until input stream reaches EOF. Maximum object size that can @@ -167,7 +167,7 @@ func (c Client) putObjectCommon(ctx context.Context, bucketName, objectName stri return c.putObjectNoChecksum(ctx, bucketName, objectName, reader, size, opts) } - // For all sizes greater than 64MiB do multipart. + // For all sizes greater than 128MiB do multipart. return c.putObjectMultipartStream(ctx, bucketName, objectName, reader, size, opts) } diff --git a/vendor/github.com/minio/minio-go/api-remove.go b/vendor/github.com/minio/minio-go/v6/api-remove.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-remove.go rename to vendor/github.com/minio/minio-go/v6/api-remove.go index 29f6f347ea..e919be17d1 100644 --- a/vendor/github.com/minio/minio-go/api-remove.go +++ b/vendor/github.com/minio/minio-go/v6/api-remove.go @@ -25,7 +25,7 @@ import ( "net/http" "net/url" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // RemoveBucket deletes the bucket name. diff --git a/vendor/github.com/minio/minio-go/api-s3-datatypes.go b/vendor/github.com/minio/minio-go/v6/api-s3-datatypes.go similarity index 100% rename from vendor/github.com/minio/minio-go/api-s3-datatypes.go rename to vendor/github.com/minio/minio-go/v6/api-s3-datatypes.go diff --git a/vendor/github.com/minio/minio-go/api-select.go b/vendor/github.com/minio/minio-go/v6/api-select.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-select.go rename to vendor/github.com/minio/minio-go/v6/api-select.go index 0b5762450f..b5ce131121 100644 --- a/vendor/github.com/minio/minio-go/api-select.go +++ b/vendor/github.com/minio/minio-go/v6/api-select.go @@ -31,8 +31,8 @@ import ( "net/url" "strings" - "github.com/minio/minio-go/pkg/encrypt" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // CSVFileHeaderInfo - is the parameter for whether to utilize headers. diff --git a/vendor/github.com/minio/minio-go/api-stat.go b/vendor/github.com/minio/minio-go/v6/api-stat.go similarity index 99% rename from vendor/github.com/minio/minio-go/api-stat.go rename to vendor/github.com/minio/minio-go/v6/api-stat.go index 15d0af39e4..c6fb47d63e 100644 --- a/vendor/github.com/minio/minio-go/api-stat.go +++ b/vendor/github.com/minio/minio-go/v6/api-stat.go @@ -24,7 +24,7 @@ import ( "strings" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // BucketExists verify if bucket exists and you have permission to access it. diff --git a/vendor/github.com/minio/minio-go/api.go b/vendor/github.com/minio/minio-go/v6/api.go similarity index 92% rename from vendor/github.com/minio/minio-go/api.go rename to vendor/github.com/minio/minio-go/v6/api.go index 0a15cb4d57..8e40219881 100644 --- a/vendor/github.com/minio/minio-go/api.go +++ b/vendor/github.com/minio/minio-go/v6/api.go @@ -41,9 +41,9 @@ import ( "golang.org/x/net/publicsuffix" - "github.com/minio/minio-go/pkg/credentials" - "github.com/minio/minio-go/pkg/s3signer" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/credentials" + "github.com/minio/minio-go/v6/pkg/s3signer" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // Client implements Amazon S3 compatible methods. @@ -73,8 +73,9 @@ type Client struct { bucketLocCache *bucketLocationCache // Advanced functionality. - isTraceEnabled bool - traceOutput io.Writer + isTraceEnabled bool + traceErrorsOnly bool + traceOutput io.Writer // S3 specific accelerated endpoint. s3AccelerateEndpoint string @@ -102,7 +103,7 @@ type Options struct { // Global constants. const ( libraryName = "minio-go" - libraryVersion = "v6.0.23" + libraryVersion = "v6.0.34" ) // User Agent should always following the below style. @@ -186,6 +187,12 @@ func NewWithOptions(endpoint string, opts *Options) (*Client, error) { return privateNew(endpoint, opts.Creds, opts.Secure, opts.Region, opts.BucketLookup) } +// EndpointURL returns the URL of the S3 endpoint. +func (c *Client) EndpointURL() *url.URL { + endpoint := *c.endpointURL // copy to prevent callers from modifying internal state + return &endpoint +} + // lockedRandSource provides protected rand source, implements rand.Source interface. type lockedRandSource struct { lk sync.Mutex @@ -263,7 +270,7 @@ func (c *Client) redirectHeaders(req *http.Request, via []*http.Request) error { case signerType.IsV2(): return errors.New("signature V2 cannot support redirection") case signerType.IsV4(): - req = s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, getDefaultLocation(*c.endpointURL, region)) + s3signer.SignV4(*req, accessKeyID, secretAccessKey, sessionToken, getDefaultLocation(*c.endpointURL, region)) } } return nil @@ -330,10 +337,6 @@ func privateNew(endpoint string, creds *credentials.Credentials, secure bool, re func (c *Client) SetAppInfo(appName string, appVersion string) { // if app name and version not set, we do not set a new user agent. if appName != "" && appVersion != "" { - c.appInfo = struct { - appName string - appVersion string - }{} c.appInfo.appName = appName c.appInfo.appVersion = appVersion } @@ -373,10 +376,23 @@ func (c *Client) TraceOn(outputStream io.Writer) { c.isTraceEnabled = true } +// TraceErrorsOnlyOn - same as TraceOn, but only errors will be traced. +func (c *Client) TraceErrorsOnlyOn(outputStream io.Writer) { + c.TraceOn(outputStream) + c.traceErrorsOnly = true +} + +// TraceErrorsOnlyOff - Turns off the errors only tracing and everything will be traced after this call. +// If all tracing needs to be turned off, call TraceOff(). +func (c *Client) TraceErrorsOnlyOff() { + c.traceErrorsOnly = false +} + // TraceOff - disable HTTP tracing. func (c *Client) TraceOff() { // Disable tracing. c.isTraceEnabled = false + c.traceErrorsOnly = false } // SetS3TransferAccelerate - turns s3 accelerated endpoint on or off for all your @@ -516,8 +532,9 @@ func (c Client) do(req *http.Request) (*http.Response, error) { return nil, ErrInvalidArgument(msg) } - // If trace is enabled, dump http request and response. - if c.isTraceEnabled { + // If trace is enabled, dump http request and response, + // except when the traceErrorsOnly enabled and the response's status code is ok + if c.isTraceEnabled && !(c.traceErrorsOnly && resp.StatusCode == http.StatusOK) { err = c.dumpHTTP(req, resp) if err != nil { return nil, err @@ -642,14 +659,30 @@ func (c Client) executeMethod(ctx context.Context, method string, metadata reque // // Additionally we should only retry if bucketLocation and custom // region is empty. - if metadata.bucketLocation == "" && c.region == "" { - if errResponse.Code == "AuthorizationHeaderMalformed" || errResponse.Code == "InvalidRegion" { + if c.region == "" { + switch errResponse.Code { + case "AuthorizationHeaderMalformed": + fallthrough + case "InvalidRegion": + fallthrough + case "AccessDenied": if metadata.bucketName != "" && errResponse.Region != "" { // Gather Cached location only if bucketName is present. - if _, cachedLocationError := c.bucketLocCache.Get(metadata.bucketName); cachedLocationError != false { + if _, cachedOk := c.bucketLocCache.Get(metadata.bucketName); cachedOk { c.bucketLocCache.Set(metadata.bucketName, errResponse.Region) continue // Retry. } + } else { + // Most probably for ListBuckets() + if errResponse.Region != metadata.bucketLocation { + // Retry if the error + // response has a + // different region + // than the request we + // just made. + metadata.bucketLocation = errResponse.Region + continue // Retry + } } } } @@ -683,13 +716,8 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R // Gather location only if bucketName is present. location, err = c.getBucketLocation(metadata.bucketName) if err != nil { - if ToErrorResponse(err).Code != "AccessDenied" { - return nil, err - } + return nil, err } - // Upon AccessDenied error on fetching bucket location, default - // to possible locations based on endpoint URL. This can usually - // happen when GetBucketLocation() is disabled using IAM policies. } if location == "" { location = getDefaultLocation(*c.endpointURL, c.region) @@ -697,10 +725,14 @@ func (c Client) newRequest(method string, metadata requestMetadata) (req *http.R } // Look if target url supports virtual host. - isVirtualHost := c.isVirtualHostStyleRequest(*c.endpointURL, metadata.bucketName) + // We explicitly disallow MakeBucket calls to not use virtual DNS style, + // since the resolution may fail. + isMakeBucket := (metadata.objectName == "" && method == "PUT" && len(metadata.queryValues) == 0) + isVirtualHost := c.isVirtualHostStyleRequest(*c.endpointURL, metadata.bucketName) && !isMakeBucket // Construct a new target URL. - targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, location, isVirtualHost, metadata.queryValues) + targetURL, err := c.makeTargetURL(metadata.bucketName, metadata.objectName, location, + isVirtualHost, metadata.queryValues) if err != nil { return nil, err } diff --git a/vendor/github.com/minio/minio-go/appveyor.yml b/vendor/github.com/minio/minio-go/v6/appveyor.yml similarity index 58% rename from vendor/github.com/minio/minio-go/appveyor.yml rename to vendor/github.com/minio/minio-go/v6/appveyor.yml index 48ea6e77dc..39a33d876d 100644 --- a/vendor/github.com/minio/minio-go/appveyor.yml +++ b/vendor/github.com/minio/minio-go/v6/appveyor.yml @@ -9,28 +9,24 @@ clone_folder: c:\gopath\src\github.com\minio\minio-go # environment variables environment: GOPATH: c:\gopath - GO15VENDOREXPERIMENT: 1 + GO111MODULE: on # scripts that run after cloning repository install: - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% - go version - go env - - go get -u golang.org/x/lint/golint - - go get -u github.com/remyoudompheng/go-misc/deadcode - - go get -u github.com/gordonklaus/ineffassign - - go get -u golang.org/x/crypto/argon2 - - go get -t ./... + - go get golang.org/x/lint/golint + - go get honnef.co/go/tools/cmd/staticcheck # to run your custom scripts instead of automatic MSBuild build_script: - go vet ./... - gofmt -s -l . - - golint -set_exit_status github.com/minio/minio-go... - - deadcode - - ineffassign . - - go test -short -v - - go test -short -race -v + - golint -set_exit_status github.com/minio/minio-go/... + - staticcheck + - go test -short -v ./... + - go test -short -race -v ./... # to disable automatic tests test: off diff --git a/vendor/github.com/minio/minio-go/bucket-cache.go b/vendor/github.com/minio/minio-go/v6/bucket-cache.go similarity index 93% rename from vendor/github.com/minio/minio-go/bucket-cache.go rename to vendor/github.com/minio/minio-go/v6/bucket-cache.go index 4ea28e5690..7ba6cbb571 100644 --- a/vendor/github.com/minio/minio-go/bucket-cache.go +++ b/vendor/github.com/minio/minio-go/v6/bucket-cache.go @@ -24,9 +24,9 @@ import ( "path" "sync" - "github.com/minio/minio-go/pkg/credentials" - "github.com/minio/minio-go/pkg/s3signer" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/credentials" + "github.com/minio/minio-go/v6/pkg/s3signer" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // bucketLocationCache - Provides simple mechanism to hold bucket @@ -124,8 +124,16 @@ func processBucketLocationResponse(resp *http.Response, bucketName string) (buck // For access denied error, it could be an anonymous // request. Move forward and let the top level callers // succeed if possible based on their policy. - if errResp.Code == "AccessDenied" { - return "us-east-1", nil + switch errResp.Code { + case "AuthorizationHeaderMalformed": + fallthrough + case "InvalidRegion": + fallthrough + case "AccessDenied": + if errResp.Region == "" { + return "us-east-1", nil + } + return errResp.Region, nil } return "", err } @@ -162,7 +170,7 @@ func (c Client) getBucketLocationRequest(bucketName string) (*http.Request, erro urlValues.Set("location", "") // Set get bucket location always as path style. - targetURL := c.endpointURL + targetURL := *c.endpointURL // as it works in makeTargetURL method from api.go file if h, p, err := net.SplitHostPort(targetURL.Host); err == nil { diff --git a/vendor/github.com/minio/minio-go/bucket-notification.go b/vendor/github.com/minio/minio-go/v6/bucket-notification.go similarity index 99% rename from vendor/github.com/minio/minio-go/bucket-notification.go rename to vendor/github.com/minio/minio-go/v6/bucket-notification.go index 11fc299fff..4714eadad5 100644 --- a/vendor/github.com/minio/minio-go/bucket-notification.go +++ b/vendor/github.com/minio/minio-go/v6/bucket-notification.go @@ -20,7 +20,7 @@ package minio import ( "encoding/xml" - "github.com/minio/minio-go/pkg/set" + "github.com/minio/minio-go/v6/pkg/set" ) // NotificationEventType is a S3 notification event associated to the bucket notification configuration diff --git a/vendor/github.com/minio/minio-go/constants.go b/vendor/github.com/minio/minio-go/v6/constants.go similarity index 95% rename from vendor/github.com/minio/minio-go/constants.go rename to vendor/github.com/minio/minio-go/v6/constants.go index 252fc5fafa..ac472a631d 100644 --- a/vendor/github.com/minio/minio-go/constants.go +++ b/vendor/github.com/minio/minio-go/v6/constants.go @@ -23,9 +23,9 @@ package minio // a part in a multipart upload may not be uploaded. const absMinPartSize = 1024 * 1024 * 5 -// minPartSize - minimum part size 64MiB per object after which +// minPartSize - minimum part size 128MiB per object after which // putObject behaves internally as multipart. -const minPartSize = 1024 * 1024 * 64 +const minPartSize = 1024 * 1024 * 128 // maxPartsCount - maximum number of parts for a single multipart session. const maxPartsCount = 10000 diff --git a/vendor/github.com/minio/minio-go/core.go b/vendor/github.com/minio/minio-go/v6/core.go similarity index 61% rename from vendor/github.com/minio/minio-go/core.go rename to vendor/github.com/minio/minio-go/v6/core.go index afd4139eed..4f57193435 100644 --- a/vendor/github.com/minio/minio-go/core.go +++ b/vendor/github.com/minio/minio-go/v6/core.go @@ -22,7 +22,7 @@ import ( "io" "strings" - "github.com/minio/minio-go/pkg/encrypt" + "github.com/minio/minio-go/v6/pkg/encrypt" ) // Core - Inherits Client and adds new methods to expose the low level S3 APIs. @@ -55,9 +55,23 @@ func (c Core) ListObjectsV2(bucketName, objectPrefix, continuationToken string, return c.listObjectsV2Query(bucketName, objectPrefix, continuationToken, fetchOwner, delimiter, maxkeys, startAfter) } +// CopyObjectWithContext - copies an object from source object to destination object on server side. +func (c Core) CopyObjectWithContext(ctx context.Context, sourceBucket, sourceObject, destBucket, destObject string, metadata map[string]string) (ObjectInfo, error) { + return c.copyObjectDo(ctx, sourceBucket, sourceObject, destBucket, destObject, metadata) +} + // CopyObject - copies an object from source object to destination object on server side. func (c Core) CopyObject(sourceBucket, sourceObject, destBucket, destObject string, metadata map[string]string) (ObjectInfo, error) { - return c.copyObjectDo(context.Background(), sourceBucket, sourceObject, destBucket, destObject, metadata) + return c.CopyObjectWithContext(context.Background(), sourceBucket, sourceObject, destBucket, destObject, metadata) +} + +// CopyObjectPartWithContext - creates a part in a multipart upload by copying (a +// part of) an existing object. +func (c Core) CopyObjectPartWithContext(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, + partID int, startOffset, length int64, metadata map[string]string) (p CompletePart, err error) { + + return c.copyObjectPartDo(ctx, srcBucket, srcObject, destBucket, destObject, uploadID, + partID, startOffset, length, metadata) } // CopyObjectPart - creates a part in a multipart upload by copying (a @@ -65,12 +79,12 @@ func (c Core) CopyObject(sourceBucket, sourceObject, destBucket, destObject stri func (c Core) CopyObjectPart(srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, startOffset, length int64, metadata map[string]string) (p CompletePart, err error) { - return c.copyObjectPartDo(context.Background(), srcBucket, srcObject, destBucket, destObject, uploadID, + return c.CopyObjectPartWithContext(context.Background(), srcBucket, srcObject, destBucket, destObject, uploadID, partID, startOffset, length, metadata) } -// PutObject - Upload object. Uploads using single PUT call. -func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, metadata map[string]string, sse encrypt.ServerSide) (ObjectInfo, error) { +// PutObjectWithContext - Upload object. Uploads using single PUT call. +func (c Core) PutObjectWithContext(ctx context.Context, bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, metadata map[string]string, sse encrypt.ServerSide) (ObjectInfo, error) { opts := PutObjectOptions{} m := make(map[string]string) for k, v := range metadata { @@ -84,7 +98,7 @@ func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Ba opts.ContentType = v } else if strings.ToLower(k) == "cache-control" { opts.CacheControl = v - } else if strings.ToLower(k) == strings.ToLower(amzWebsiteRedirectLocation) { + } else if strings.EqualFold(k, amzWebsiteRedirectLocation) { opts.WebsiteRedirectLocation = v } else { m[k] = metadata[k] @@ -92,7 +106,12 @@ func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Ba } opts.UserMetadata = m opts.ServerSideEncryption = sse - return c.putObjectDo(context.Background(), bucket, object, data, md5Base64, sha256Hex, size, opts) + return c.putObjectDo(ctx, bucket, object, data, md5Base64, sha256Hex, size, opts) +} + +// PutObject - Upload object. Uploads using single PUT call. +func (c Core) PutObject(bucket, object string, data io.Reader, size int64, md5Base64, sha256Hex string, metadata map[string]string, sse encrypt.ServerSide) (ObjectInfo, error) { + return c.PutObjectWithContext(context.Background(), bucket, object, data, size, md5Base64, sha256Hex, metadata, sse) } // NewMultipartUpload - Initiates new multipart upload and returns the new uploadID. @@ -106,9 +125,14 @@ func (c Core) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, de return c.listMultipartUploadsQuery(bucket, keyMarker, uploadIDMarker, prefix, delimiter, maxUploads) } +// PutObjectPartWithContext - Upload an object part. +func (c Core) PutObjectPartWithContext(ctx context.Context, bucket, object, uploadID string, partID int, data io.Reader, size int64, md5Base64, sha256Hex string, sse encrypt.ServerSide) (ObjectPart, error) { + return c.uploadPart(ctx, bucket, object, uploadID, data, partID, md5Base64, sha256Hex, size, sse) +} + // PutObjectPart - Upload an object part. func (c Core) PutObjectPart(bucket, object, uploadID string, partID int, data io.Reader, size int64, md5Base64, sha256Hex string, sse encrypt.ServerSide) (ObjectPart, error) { - return c.uploadPart(context.Background(), bucket, object, uploadID, data, partID, md5Base64, sha256Hex, size, sse) + return c.PutObjectPartWithContext(context.Background(), bucket, object, uploadID, partID, data, size, md5Base64, sha256Hex, sse) } // ListObjectParts - List uploaded parts of an incomplete upload.x @@ -116,17 +140,27 @@ func (c Core) ListObjectParts(bucket, object, uploadID string, partNumberMarker return c.listObjectPartsQuery(bucket, object, uploadID, partNumberMarker, maxParts) } -// CompleteMultipartUpload - Concatenate uploaded parts and commit to an object. -func (c Core) CompleteMultipartUpload(bucket, object, uploadID string, parts []CompletePart) (string, error) { - res, err := c.completeMultipartUpload(context.Background(), bucket, object, uploadID, completeMultipartUpload{ +// CompleteMultipartUploadWithContext - Concatenate uploaded parts and commit to an object. +func (c Core) CompleteMultipartUploadWithContext(ctx context.Context, bucket, object, uploadID string, parts []CompletePart) (string, error) { + res, err := c.completeMultipartUpload(ctx, bucket, object, uploadID, completeMultipartUpload{ Parts: parts, }) return res.ETag, err } +// CompleteMultipartUpload - Concatenate uploaded parts and commit to an object. +func (c Core) CompleteMultipartUpload(bucket, object, uploadID string, parts []CompletePart) (string, error) { + return c.CompleteMultipartUploadWithContext(context.Background(), bucket, object, uploadID, parts) +} + +// AbortMultipartUploadWithContext - Abort an incomplete upload. +func (c Core) AbortMultipartUploadWithContext(ctx context.Context, bucket, object, uploadID string) error { + return c.abortMultipartUpload(ctx, bucket, object, uploadID) +} + // AbortMultipartUpload - Abort an incomplete upload. func (c Core) AbortMultipartUpload(bucket, object, uploadID string) error { - return c.abortMultipartUpload(context.Background(), bucket, object, uploadID) + return c.AbortMultipartUploadWithContext(context.Background(), bucket, object, uploadID) } // GetBucketPolicy - fetches bucket access policy for a given bucket. @@ -139,15 +173,28 @@ func (c Core) PutBucketPolicy(bucket, bucketPolicy string) error { return c.putBucketPolicy(bucket, bucketPolicy) } +// GetObjectWithContext is a lower level API implemented to support reading +// partial objects and also downloading objects with special conditions +// matching etag, modtime etc. +func (c Core) GetObjectWithContext(ctx context.Context, bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) { + return c.getObject(ctx, bucketName, objectName, opts) +} + // GetObject is a lower level API implemented to support reading // partial objects and also downloading objects with special conditions // matching etag, modtime etc. func (c Core) GetObject(bucketName, objectName string, opts GetObjectOptions) (io.ReadCloser, ObjectInfo, error) { - return c.getObject(context.Background(), bucketName, objectName, opts) + return c.GetObjectWithContext(context.Background(), bucketName, objectName, opts) +} + +// StatObjectWithContext is a lower level API implemented to support special +// conditions matching etag, modtime on a request. +func (c Core) StatObjectWithContext(ctx context.Context, bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { + return c.statObject(ctx, bucketName, objectName, opts) } // StatObject is a lower level API implemented to support special // conditions matching etag, modtime on a request. func (c Core) StatObject(bucketName, objectName string, opts StatObjectOptions) (ObjectInfo, error) { - return c.statObject(context.Background(), bucketName, objectName, opts) + return c.StatObjectWithContext(context.Background(), bucketName, objectName, opts) } diff --git a/vendor/github.com/minio/minio-go/functional_tests.go b/vendor/github.com/minio/minio-go/v6/functional_tests.go similarity index 98% rename from vendor/github.com/minio/minio-go/functional_tests.go rename to vendor/github.com/minio/minio-go/v6/functional_tests.go index b412edb23f..463552ce12 100644 --- a/vendor/github.com/minio/minio-go/functional_tests.go +++ b/vendor/github.com/minio/minio-go/v6/functional_tests.go @@ -40,10 +40,10 @@ import ( "time" humanize "github.com/dustin/go-humanize" - minio "github.com/minio/minio-go" log "github.com/sirupsen/logrus" - "github.com/minio/minio-go/pkg/encrypt" + "github.com/minio/minio-go/v6" + "github.com/minio/minio-go/v6/pkg/encrypt" ) const letterBytes = "abcdefghijklmnopqrstuvwxyz01234569" @@ -184,9 +184,9 @@ func isErrNotImplemented(err error) bool { func init() { // If server endpoint is not set, all tests default to - // using https://play.min.io:9000 + // using https://play.min.io if os.Getenv(serverEndpoint) == "" { - os.Setenv(serverEndpoint, "play.min.io:9000") + os.Setenv(serverEndpoint, "play.min.io") os.Setenv(accessKey, "Q3AM3UQ867SPQQA43P2F") os.Setenv(secretKey, "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG") os.Setenv(enableHTTPS, "1") @@ -264,7 +264,7 @@ var dataFileMap = map[string]int{ "datafile-5-MB": 5 * humanize.MiByte, "datafile-6-MB": 6 * humanize.MiByte, "datafile-11-MB": 11 * humanize.MiByte, - "datafile-65-MB": 65 * humanize.MiByte, + "datafile-129-MB": 129 * humanize.MiByte, } func isFullMode() bool { @@ -531,8 +531,8 @@ func testPutObjectReadAt() { return } - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() // Save the data @@ -641,8 +641,8 @@ func testPutObjectWithMetadata() { return } - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() // Save the data @@ -1308,7 +1308,7 @@ func testFPutObjectMultipart() { } // Upload 4 parts to utilize all 3 'workers' in multipart and still have a part to upload. - var fileName = getMintDataDirFilePath("datafile-65-MB") + var fileName = getMintDataDirFilePath("datafile-129-MB") if fileName == "" { // Make a temp file with minPartSize bytes of data. file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest") @@ -1317,7 +1317,7 @@ func testFPutObjectMultipart() { return } // Upload 2 parts to utilize all 3 'workers' in multipart and still have a part to upload. - if _, err = io.Copy(file, getDataReader("datafile-65-MB")); err != nil { + if _, err = io.Copy(file, getDataReader("datafile-129-MB")); err != nil { logError(testName, function, args, startTime, "", "Copy failed", err) return } @@ -1328,7 +1328,7 @@ func testFPutObjectMultipart() { fileName = file.Name() args["fileName"] = fileName } - totalSize := dataFileMap["datafile-65-MB"] + totalSize := dataFileMap["datafile-129-MB"] // Set base object name objectName := bucketName + "FPutObject" + "-standard" args["objectName"] = objectName @@ -1426,7 +1426,7 @@ func testFPutObject() { // Upload 3 parts worth of data to use all 3 of multiparts 'workers' and have an extra part. // Use different data in part for multipart tests to check parts are uploaded in correct order. - var fName = getMintDataDirFilePath("datafile-65-MB") + var fName = getMintDataDirFilePath("datafile-129-MB") if fName == "" { // Make a temp file with minPartSize bytes of data. file, err := ioutil.TempFile(os.TempDir(), "FPutObjectTest") @@ -1436,7 +1436,7 @@ func testFPutObject() { } // Upload 3 parts to utilize all 3 'workers' in multipart and still have a part to upload. - if _, err = io.Copy(file, getDataReader("datafile-65-MB")); err != nil { + if _, err = io.Copy(file, getDataReader("datafile-129-MB")); err != nil { logError(testName, function, args, startTime, "", "File copy failed", err) return } @@ -1448,7 +1448,7 @@ func testFPutObject() { defer os.Remove(file.Name()) fName = file.Name() } - totalSize := dataFileMap["datafile-65-MB"] + totalSize := dataFileMap["datafile-129-MB"] // Set base object name function = "FPutObject(bucketName, objectName, fileName, opts)" @@ -2226,6 +2226,133 @@ func testGetObjectReadAtFunctional() { successLogger(testName, function, args, startTime).Info() } +// Reproduces issue https://github.com/minio/minio-go/issues/1137 +func testGetObjectReadAtWhenEOFWasReached() { + // initialize logging params + startTime := time.Now() + testName := getFuncName() + function := "GetObject(bucketName, objectName)" + args := map[string]interface{}{} + + // Seed random based on current time. + rand.Seed(time.Now().Unix()) + + // Instantiate new minio client object. + c, err := minio.New( + os.Getenv(serverEndpoint), + os.Getenv(accessKey), + os.Getenv(secretKey), + mustParseBool(os.Getenv(enableHTTPS)), + ) + if err != nil { + logError(testName, function, args, startTime, "", "MinIO client object creation failed", err) + return + } + + // Enable tracing, write to stderr. + // c.TraceOn(os.Stderr) + + // Set user agent. + c.SetAppInfo("MinIO-go-FunctionalTest", "0.1.0") + + // Generate a new random bucket name. + bucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "minio-go-test-") + args["bucketName"] = bucketName + + // Make a new bucket. + err = c.MakeBucket(bucketName, "us-east-1") + if err != nil { + logError(testName, function, args, startTime, "", "MakeBucket failed", err) + return + } + + // Generate 33K of data. + bufSize := dataFileMap["datafile-33-kB"] + var reader = getDataReader("datafile-33-kB") + defer reader.Close() + + objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") + args["objectName"] = objectName + + buf, err := ioutil.ReadAll(reader) + if err != nil { + logError(testName, function, args, startTime, "", "ReadAll failed", err) + return + } + + // Save the data + n, err := c.PutObject(bucketName, objectName, bytes.NewReader(buf), int64(len(buf)), minio.PutObjectOptions{ContentType: "binary/octet-stream"}) + if err != nil { + logError(testName, function, args, startTime, "", "PutObject failed", err) + return + } + + if n != int64(bufSize) { + logError(testName, function, args, startTime, "", "Number of bytes does not match, expected "+string(int64(bufSize))+", got "+string(n), err) + return + } + + // read the data back + r, err := c.GetObject(bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + logError(testName, function, args, startTime, "", "PutObject failed", err) + return + } + + // read directly + buf1 := make([]byte, n) + buf2 := make([]byte, 512) + + m, err := r.Read(buf1) + if err != nil { + if err != io.EOF { + logError(testName, function, args, startTime, "", "Read failed", err) + return + } + } + if m != len(buf1) { + logError(testName, function, args, startTime, "", "Read read shorter bytes before reaching EOF, expected "+string(len(buf1))+", got "+string(m), err) + return + } + if !bytes.Equal(buf1, buf) { + logError(testName, function, args, startTime, "", "Incorrect count of Read data", err) + return + } + + st, err := r.Stat() + if err != nil { + logError(testName, function, args, startTime, "", "Stat failed", err) + return + } + + if st.Size != int64(bufSize) { + logError(testName, function, args, startTime, "", "Number of bytes in stat does not match, expected "+string(int64(bufSize))+", got "+string(st.Size), err) + return + } + + m, err = r.ReadAt(buf2, 512) + if err != nil { + logError(testName, function, args, startTime, "", "ReadAt failed", err) + return + } + if m != len(buf2) { + logError(testName, function, args, startTime, "", "ReadAt read shorter bytes before reaching EOF, expected "+string(len(buf2))+", got "+string(m), err) + return + } + if !bytes.Equal(buf2, buf[512:1024]) { + logError(testName, function, args, startTime, "", "Incorrect count of ReadAt data", err) + return + } + + // Delete all objects and buckets + if err = cleanupBucket(bucketName, c); err != nil { + logError(testName, function, args, startTime, "", "Cleanup failed", err) + return + } + + successLogger(testName, function, args, startTime).Info() +} + // Test Presigned Post Policy func testPresignedPostPolicy() { // initialize logging params @@ -2706,9 +2833,9 @@ func testSSECEncryptedGetObjectReadSeekFunctional() { } }() - // Generate 65MiB of data. - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + // Generate 129MiB of data. + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") @@ -2894,9 +3021,9 @@ func testSSES3EncryptedGetObjectReadSeekFunctional() { } }() - // Generate 65MiB of data. - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + // Generate 129MiB of data. + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") @@ -3072,9 +3199,9 @@ func testSSECEncryptedGetObjectReadAtFunctional() { return } - // Generate 65MiB of data. - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + // Generate 129MiB of data. + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") @@ -3263,9 +3390,9 @@ func testSSES3EncryptedGetObjectReadAtFunctional() { return } - // Generate 65MiB of data. - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + // Generate 129MiB of data. + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() objectName := randString(60, rand.NewSource(time.Now().UnixNano()), "") @@ -8561,8 +8688,8 @@ func testPutObjectNoLengthV2() { objectName := bucketName + "unique" args["objectName"] = objectName - bufSize := dataFileMap["datafile-65-MB"] - var reader = getDataReader("datafile-65-MB") + bufSize := dataFileMap["datafile-129-MB"] + var reader = getDataReader("datafile-129-MB") defer reader.Close() args["size"] = bufSize @@ -10004,6 +10131,7 @@ func main() { testFPutObject() testGetObjectReadSeekFunctional() testGetObjectReadAtFunctional() + testGetObjectReadAtWhenEOFWasReached() testPresignedPostPolicy() testCopyObject() testComposeObjectErrorCases() diff --git a/vendor/github.com/minio/minio-go/v6/go.mod b/vendor/github.com/minio/minio-go/v6/go.mod new file mode 100644 index 0000000000..9d342468df --- /dev/null +++ b/vendor/github.com/minio/minio-go/v6/go.mod @@ -0,0 +1,16 @@ +module github.com/minio/minio-go/v6 + +go 1.12 + +require ( + github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/gernest/wow v0.1.0 // indirect + github.com/minio/cli v1.20.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 + github.com/sirupsen/logrus v1.4.2 // indirect + github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect + golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f + golang.org/x/net v0.0.0-20190522155817-f3200d17e092 + gopkg.in/ini.v1 v1.42.0 +) diff --git a/vendor/github.com/minio/minio-go/v6/go.sum b/vendor/github.com/minio/minio-go/v6/go.sum new file mode 100644 index 0000000000..62a4f31fac --- /dev/null +++ b/vendor/github.com/minio/minio-go/v6/go.sum @@ -0,0 +1,48 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845 h1:hIjQrEARcc9LcH8igte3JBpWBZ7+SpinU70dOjU/afo= +github.com/a8m/mark v0.1.1-0.20170507133748-44f2db618845/go.mod h1:c8Mh99Cw82nrsAnPgxQSZHkswVOJF7/MqZb1ZdvriLM= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/gernest/wow v0.1.0 h1:g9xdwCwP0+xgVYlA2sopI0gZHqXe7HjI/7/LykG4fks= +github.com/gernest/wow v0.1.0/go.mod h1:dEPabJRi5BneI1Nev1VWo0ZlcTWibHWp43qxKms4elY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/minio/cli v1.20.0 h1:OVNIt8Rg5+mpYb8siWT2gBV5hvUyFbRvBikC+Ytvf5A= +github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/minio/minio-go/hook-reader.go b/vendor/github.com/minio/minio-go/v6/hook-reader.go similarity index 100% rename from vendor/github.com/minio/minio-go/hook-reader.go rename to vendor/github.com/minio/minio-go/v6/hook-reader.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/chain.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/chain.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/chain.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/chain.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/config.json.sample b/vendor/github.com/minio/minio-go/v6/pkg/credentials/config.json.sample similarity index 88% rename from vendor/github.com/minio/minio-go/pkg/credentials/config.json.sample rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/config.json.sample index 0affa58cf5..d793c9e0e9 100644 --- a/vendor/github.com/minio/minio-go/pkg/credentials/config.json.sample +++ b/vendor/github.com/minio/minio-go/v6/pkg/credentials/config.json.sample @@ -2,7 +2,7 @@ "version": "8", "hosts": { "play": { - "url": "https://play.min.io:9000", + "url": "https://play.min.io", "accessKey": "Q3AM3UQ867SPQQA43P2F", "secretKey": "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", "api": "S3v2" diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/credentials.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/credentials.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/credentials.sample b/vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.sample similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/credentials.sample rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/credentials.sample diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/doc.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/doc.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/doc.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/doc.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/env_aws.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/env_aws.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/env_aws.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/env_aws.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/env_minio.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/env_minio.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/env_minio.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/env_minio.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/file_aws_credentials.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/file_aws_credentials.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/file_aws_credentials.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/file_aws_credentials.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/file_minio_client.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/file_minio_client.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/file_minio_client.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/file_minio_client.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/iam_aws.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/iam_aws.go similarity index 99% rename from vendor/github.com/minio/minio-go/pkg/credentials/iam_aws.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/iam_aws.go index 310785209d..5732f2e4b6 100644 --- a/vendor/github.com/minio/minio-go/pkg/credentials/iam_aws.go +++ b/vendor/github.com/minio/minio-go/v6/pkg/credentials/iam_aws.go @@ -53,7 +53,7 @@ type IAM struct { const ( defaultIAMRoleEndpoint = "http://169.254.169.254" defaultECSRoleEndpoint = "http://169.254.170.2" - defaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials" + defaultIAMSecurityCredsPath = "/latest/meta-data/iam/security-credentials/" ) // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/signature-type.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/signature-type.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/signature-type.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/signature-type.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/static.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/static.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/static.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/static.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/sts_client_grants.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_client_grants.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/sts_client_grants.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_client_grants.go diff --git a/vendor/github.com/minio/minio-go/pkg/credentials/sts_web_identity.go b/vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_web_identity.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/credentials/sts_web_identity.go rename to vendor/github.com/minio/minio-go/v6/pkg/credentials/sts_web_identity.go diff --git a/vendor/github.com/minio/minio-go/pkg/encrypt/server-side.go b/vendor/github.com/minio/minio-go/v6/pkg/encrypt/server-side.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/encrypt/server-side.go rename to vendor/github.com/minio/minio-go/v6/pkg/encrypt/server-side.go diff --git a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-streaming.go b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-streaming.go similarity index 99% rename from vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-streaming.go rename to vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-streaming.go index c82e6bd372..810b47c4b3 100644 --- a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-streaming.go +++ b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-streaming.go @@ -285,7 +285,7 @@ func (s *StreamingReader) Read(buf []byte) (int, error) { // bytes read from baseReader different than // content length provided. if s.bytesRead != s.contentLen { - return 0, io.ErrUnexpectedEOF + return 0, fmt.Errorf("http: ContentLength=%d with Body length %d", s.contentLen, s.bytesRead) } // Sign the chunk and write it to s.buf. diff --git a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v2.go similarity index 99% rename from vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go rename to vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v2.go index 919540690b..40ba07130a 100644 --- a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v2.go +++ b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v2.go @@ -30,7 +30,7 @@ import ( "strings" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // Signature and API related constants. diff --git a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v4.go similarity index 99% rename from vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go rename to vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v4.go index e23c06baf6..ab96b58c52 100644 --- a/vendor/github.com/minio/minio-go/pkg/s3signer/request-signature-v4.go +++ b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/request-signature-v4.go @@ -26,7 +26,7 @@ import ( "strings" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // Signature and API related constants. diff --git a/vendor/github.com/minio/minio-go/pkg/s3signer/utils.go b/vendor/github.com/minio/minio-go/v6/pkg/s3signer/utils.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/s3signer/utils.go rename to vendor/github.com/minio/minio-go/v6/pkg/s3signer/utils.go diff --git a/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go b/vendor/github.com/minio/minio-go/v6/pkg/s3utils/utils.go similarity index 98% rename from vendor/github.com/minio/minio-go/pkg/s3utils/utils.go rename to vendor/github.com/minio/minio-go/v6/pkg/s3utils/utils.go index c80cfba7eb..9af2997b74 100644 --- a/vendor/github.com/minio/minio-go/pkg/s3utils/utils.go +++ b/vendor/github.com/minio/minio-go/v6/pkg/s3utils/utils.go @@ -47,8 +47,8 @@ func IsValidDomain(host string) bool { if host[len(host)-1:] == "_" || host[:1] == "_" { return false } - // host cannot start or end with a "." - if host[len(host)-1:] == "." || host[:1] == "." { + // host cannot start with a "." + if host[:1] == "." { return false } // All non alphanumeric characters are invalid. @@ -282,7 +282,7 @@ func checkBucketNameCommon(bucketName string, strict bool) (err error) { if ipAddress.MatchString(bucketName) { return errors.New("Bucket name cannot be an ip address") } - if strings.Contains(bucketName, "..") { + if strings.Contains(bucketName, "..") || strings.Contains(bucketName, ".-") || strings.Contains(bucketName, "-.") { return errors.New("Bucket name contains invalid characters") } if strict { diff --git a/vendor/github.com/minio/minio-go/pkg/set/stringset.go b/vendor/github.com/minio/minio-go/v6/pkg/set/stringset.go similarity index 100% rename from vendor/github.com/minio/minio-go/pkg/set/stringset.go rename to vendor/github.com/minio/minio-go/v6/pkg/set/stringset.go diff --git a/vendor/github.com/minio/minio-go/post-policy.go b/vendor/github.com/minio/minio-go/v6/post-policy.go similarity index 100% rename from vendor/github.com/minio/minio-go/post-policy.go rename to vendor/github.com/minio/minio-go/v6/post-policy.go diff --git a/vendor/github.com/minio/minio-go/retry-continous.go b/vendor/github.com/minio/minio-go/v6/retry-continous.go similarity index 100% rename from vendor/github.com/minio/minio-go/retry-continous.go rename to vendor/github.com/minio/minio-go/v6/retry-continous.go diff --git a/vendor/github.com/minio/minio-go/retry.go b/vendor/github.com/minio/minio-go/v6/retry.go similarity index 95% rename from vendor/github.com/minio/minio-go/retry.go rename to vendor/github.com/minio/minio-go/v6/retry.go index bd16fbb5ad..2c608bab54 100644 --- a/vendor/github.com/minio/minio-go/retry.go +++ b/vendor/github.com/minio/minio-go/v6/retry.go @@ -111,6 +111,9 @@ func isHTTPReqErrorRetryable(err error) bool { } else if strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken") { // If error is transport connection broken, retry. return true + } else if strings.Contains(err.Error(), "net/http: timeout awaiting response headers") { + // Retry errors due to server not sending the response before timeout + return true } } return false @@ -143,6 +146,7 @@ var retryableHTTPStatusCodes = map[int]struct{}{ http.StatusInternalServerError: {}, http.StatusBadGateway: {}, http.StatusServiceUnavailable: {}, + http.StatusGatewayTimeout: {}, // Add more HTTP status codes here. } diff --git a/vendor/github.com/minio/minio-go/s3-endpoints.go b/vendor/github.com/minio/minio-go/v6/s3-endpoints.go similarity index 97% rename from vendor/github.com/minio/minio-go/s3-endpoints.go rename to vendor/github.com/minio/minio-go/v6/s3-endpoints.go index ef4ae74b5e..989f58c7ec 100644 --- a/vendor/github.com/minio/minio-go/s3-endpoints.go +++ b/vendor/github.com/minio/minio-go/v6/s3-endpoints.go @@ -29,6 +29,7 @@ var awsS3EndpointMap = map[string]string{ "eu-west-3": "s3.dualstack.eu-west-3.amazonaws.com", "eu-central-1": "s3.dualstack.eu-central-1.amazonaws.com", "eu-north-1": "s3.dualstack.eu-north-1.amazonaws.com", + "ap-east-1": "s3.dualstack.ap-east-1.amazonaws.com", "ap-south-1": "s3.dualstack.ap-south-1.amazonaws.com", "ap-southeast-1": "s3.dualstack.ap-southeast-1.amazonaws.com", "ap-southeast-2": "s3.dualstack.ap-southeast-2.amazonaws.com", diff --git a/vendor/github.com/minio/minio-go/s3-error.go b/vendor/github.com/minio/minio-go/v6/s3-error.go similarity index 100% rename from vendor/github.com/minio/minio-go/s3-error.go rename to vendor/github.com/minio/minio-go/v6/s3-error.go diff --git a/vendor/github.com/minio/minio-go/v6/staticcheck.conf b/vendor/github.com/minio/minio-go/v6/staticcheck.conf new file mode 100644 index 0000000000..71cc6f536a --- /dev/null +++ b/vendor/github.com/minio/minio-go/v6/staticcheck.conf @@ -0,0 +1 @@ +checks = ["all", "-ST1005", "-ST1017", "-SA9004", "-ST1000", "-S1021"] \ No newline at end of file diff --git a/vendor/github.com/minio/minio-go/transport.go b/vendor/github.com/minio/minio-go/v6/transport.go similarity index 100% rename from vendor/github.com/minio/minio-go/transport.go rename to vendor/github.com/minio/minio-go/v6/transport.go diff --git a/vendor/github.com/minio/minio-go/utils.go b/vendor/github.com/minio/minio-go/v6/utils.go similarity index 98% rename from vendor/github.com/minio/minio-go/utils.go rename to vendor/github.com/minio/minio-go/v6/utils.go index dbad3b103b..fc30c1ab7c 100644 --- a/vendor/github.com/minio/minio-go/utils.go +++ b/vendor/github.com/minio/minio-go/v6/utils.go @@ -32,7 +32,7 @@ import ( "strings" "time" - "github.com/minio/minio-go/pkg/s3utils" + "github.com/minio/minio-go/v6/pkg/s3utils" ) // xmlDecoder provide decoded value in xml. @@ -229,7 +229,7 @@ var supportedHeaders = []string{ // isStorageClassHeader returns true if the header is a supported storage class header func isStorageClassHeader(headerKey string) bool { - return strings.ToLower(amzStorageClass) == strings.ToLower(headerKey) + return strings.EqualFold(amzStorageClass, headerKey) } // isStandardHeader returns true if header is a supported header and not a custom header diff --git a/vendor/modules.txt b/vendor/modules.txt index 386d6938bb..7a1125ff2a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -140,13 +140,13 @@ github.com/mattn/go-sqlite3 github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/miekg/dns v1.1.15 github.com/miekg/dns -# github.com/minio/minio-go v0.0.0-20190422205105-a8704b60278f -github.com/minio/minio-go -github.com/minio/minio-go/pkg/credentials -github.com/minio/minio-go/pkg/encrypt -github.com/minio/minio-go/pkg/s3signer -github.com/minio/minio-go/pkg/s3utils -github.com/minio/minio-go/pkg/set +# github.com/minio/minio-go/v6 v6.0.34 +github.com/minio/minio-go/v6 +github.com/minio/minio-go/v6/pkg/credentials +github.com/minio/minio-go/v6/pkg/encrypt +github.com/minio/minio-go/v6/pkg/s3signer +github.com/minio/minio-go/v6/pkg/s3utils +github.com/minio/minio-go/v6/pkg/set # github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir # github.com/mitchellh/go-testing-interface v1.0.0 From 40b5db763734c05fede95cf5fcf480f272149c5d Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 10:39:55 +0200 Subject: [PATCH 39/47] Converting to structured logging the file store/sqlstore/session_store.go (#12088) --- store/sqlstore/session_store.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/store/sqlstore/session_store.go b/store/sqlstore/session_store.go index 222ef4ba5e..c8c58b5546 100644 --- a/store/sqlstore/session_store.go +++ b/store/sqlstore/session_store.go @@ -4,7 +4,6 @@ package sqlstore import ( - "fmt" "net/http" "time" @@ -251,13 +250,13 @@ func (me SqlSessionStore) Cleanup(expiryTime int64, batchSize int64) { for rowsAffected > 0 { if sqlResult, err := me.GetMaster().Exec(query, map[string]interface{}{"ExpiresAt": expiryTime, "Limit": batchSize}); err != nil { - mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error())) + mlog.Error("Unable to cleanup session store.", mlog.Err(err)) return } else { var rowErr error rowsAffected, rowErr = sqlResult.RowsAffected() if rowErr != nil { - mlog.Error(fmt.Sprintf("Unable to cleanup session store. err=%v", err.Error())) + mlog.Error("Unable to cleanup session store.", mlog.Err(err)) return } } From ae05429ffe34f2767dbf3a587e3e5edfb0be918f Mon Sep 17 00:00:00 2001 From: Ben Schumacher Date: Tue, 10 Sep 2019 10:56:57 +0200 Subject: [PATCH 40/47] Add empty circleci file (#12071) * Add empty circleci file --- .circleci/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..b86d194ae8 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,8 @@ +version: 2.1 + +jobs: + build: + docker: + - image: circleci/golang:1.12 + steps: + - run: echo "skipping build. PR \#11978 in progress." From 9094aa7f97226ac7d79777a333aadac7c3658ce8 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 11:42:05 +0200 Subject: [PATCH 41/47] Converting to structured logging the file store/sqlstore/supplier.go (#12089) * Converting to structured logging the file store/sqlstore/supplier.go * removed unused : from comments --- store/sqlstore/supplier.go | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 1a3de5eb3d..d6a5cf9f83 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -159,14 +159,14 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter err := supplier.GetMaster().CreateTablesIfNotExists() if err != nil { - mlog.Critical(fmt.Sprintf("Error creating database tables: %v", err)) + mlog.Critical("Error creating database tables.", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_TABLE) } err = UpgradeDatabase(supplier, model.CurrentVersion) if err != nil { - mlog.Critical("Failed to upgrade database", mlog.Err(err)) + mlog.Critical("Failed to upgrade database.", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_GENERIC_FAILURE) } @@ -214,13 +214,13 @@ func (s *SqlSupplier) Next() store.LayeredStoreSupplier { func setupConnection(con_type string, dataSource string, settings *model.SqlSettings) *gorp.DbMap { db, err := dbsql.Open(*settings.DriverName, dataSource) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to open SQL connection to err:%v", err.Error())) + mlog.Critical("Failed to open SQL connection to err.", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_DB_OPEN) } for i := 0; i < DB_PING_ATTEMPTS; i++ { - mlog.Info(fmt.Sprintf("Pinging SQL %v database", con_type)) + mlog.Info("Pinging SQL", mlog.String("database", con_type)) ctx, cancel := context.WithTimeout(context.Background(), DB_PING_TIMEOUT_SECS*time.Second) defer cancel() err = db.PingContext(ctx) @@ -228,11 +228,11 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett break } else { if i == DB_PING_ATTEMPTS-1 { - mlog.Critical(fmt.Sprintf("Failed to ping DB, server will exit err=%v", err)) + mlog.Critical("Failed to ping DB, server will exit.", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_PING) } else { - mlog.Error(fmt.Sprintf("Failed to ping DB retrying in %v seconds err=%v", DB_PING_TIMEOUT_SECS, err)) + mlog.Error("Failed to ping DB", mlog.Err(err), mlog.Int("retrying in seconds", DB_PING_TIMEOUT_SECS)) time.Sleep(DB_PING_TIMEOUT_SECS * time.Second) } } @@ -365,7 +365,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { ) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err)) + mlog.Critical("Failed to check if table exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_TABLE_EXISTS) } @@ -387,7 +387,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { ) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err)) + mlog.Critical("Failed to check if table exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_TABLE_EXISTS_MYSQL) } @@ -401,7 +401,7 @@ func (ss *SqlSupplier) DoesTableExist(tableName string) bool { ) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if table exists %v", err)) + mlog.Critical("Failed to check if table exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_TABLE_EXISTS_SQLITE) } @@ -433,7 +433,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool return false } - mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err)) + mlog.Critical("Failed to check if column exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_POSTGRES) } @@ -456,7 +456,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool ) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err)) + mlog.Critical("Failed to check if column exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_MYSQL) } @@ -471,7 +471,7 @@ func (ss *SqlSupplier) DoesColumnExist(tableName string, columnName string) bool ) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if column exists %v", err)) + mlog.Critical("Failed to check if column exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_DOES_COLUMN_EXISTS_SQLITE) } @@ -498,7 +498,7 @@ func (ss *SqlSupplier) DoesTriggerExist(triggerName string) bool { `, triggerName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if trigger exists %v", err)) + mlog.Critical("Failed to check if trigger exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_GENERIC_FAILURE) } @@ -517,7 +517,7 @@ func (ss *SqlSupplier) DoesTriggerExist(triggerName string) bool { `, triggerName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check if trigger exists %v", err)) + mlog.Critical("Failed to check if trigger exists", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_GENERIC_FAILURE) } @@ -541,7 +541,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType + " DEFAULT '" + defaultValue + "'") if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) + mlog.Critical("Failed to create column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_POSTGRES) } @@ -551,7 +551,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExists(tableName string, columnName stri } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType + " DEFAULT '" + defaultValue + "'") if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) + mlog.Critical("Failed to create column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_MYSQL) } @@ -575,7 +575,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExistsNoDefault(tableName string, column if ss.DriverName() == model.DATABASE_DRIVER_POSTGRES { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + postgresColType) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) + mlog.Critical("Failed to create column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_POSTGRES) } @@ -585,7 +585,7 @@ func (ss *SqlSupplier) CreateColumnIfNotExistsNoDefault(tableName string, column } else if ss.DriverName() == model.DATABASE_DRIVER_MYSQL { _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " ADD " + columnName + " " + mySqlColType) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create column %v", err)) + mlog.Critical("Failed to create column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_COLUMN_MYSQL) } @@ -608,7 +608,7 @@ func (ss *SqlSupplier) RemoveColumnIfExists(tableName string, columnName string) _, err := ss.GetMaster().ExecNoTimeout("ALTER TABLE " + tableName + " DROP COLUMN " + columnName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to drop column %v", err)) + mlog.Critical("Failed to drop column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_COLUMN) } @@ -623,7 +623,7 @@ func (ss *SqlSupplier) RemoveTableIfExists(tableName string) bool { _, err := ss.GetMaster().ExecNoTimeout("DROP TABLE " + tableName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to drop table %v", err)) + mlog.Critical("Failed to drop table", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_TABLE) } @@ -644,7 +644,7 @@ func (ss *SqlSupplier) RenameColumnIfExists(tableName string, oldColumnName stri } if err != nil { - mlog.Critical(fmt.Sprintf("Failed to rename column %v", err)) + mlog.Critical("Failed to rename column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_RENAME_COLUMN) } @@ -666,7 +666,7 @@ func (ss *SqlSupplier) GetMaxLengthOfColumnIfExists(tableName string, columnName } if err != nil { - mlog.Critical(fmt.Sprintf("Failed to get max length of column %v", err)) + mlog.Critical("Failed to get max length of column", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_MAX_COLUMN) } @@ -687,7 +687,7 @@ func (ss *SqlSupplier) AlterColumnTypeIfExists(tableName string, columnName stri } if err != nil { - mlog.Critical(fmt.Sprintf("Failed to alter column type %v", err)) + mlog.Critical("Failed to alter column type", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_ALTER_COLUMN) } @@ -736,7 +736,7 @@ func (ss *SqlSupplier) AlterColumnDefaultIfExists(tableName string, columnName s } if err != nil { - mlog.Critical(fmt.Sprintf("Failed to alter column %s.%s default %s: %v", tableName, columnName, defaultValue, err)) + mlog.Critical("Failed to alter column", mlog.String("table", tableName), mlog.String("column", columnName), mlog.String("default value", defaultValue), mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_GENERIC_FAILURE) return false @@ -790,7 +790,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string _, err := ss.GetMaster().ExecNoTimeout(query) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create index %v, %v", errExists, err)) + mlog.Critical("Failed to create index", mlog.Err(errExists), mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_POSTGRES) } @@ -798,7 +798,7 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check index %v", err)) + mlog.Critical("Failed to check index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_MYSQL) } @@ -814,14 +814,14 @@ func (ss *SqlSupplier) createIndexIfNotExists(indexName string, tableName string _, err = ss.GetMaster().ExecNoTimeout("CREATE " + uniqueStr + fullTextIndex + " INDEX " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create index %v", err)) + mlog.Critical("Failed to create index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_FULL_MYSQL) } } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { _, err := ss.GetMaster().ExecNoTimeout("CREATE INDEX IF NOT EXISTS " + indexName + " ON " + tableName + " (" + strings.Join(columnNames, ", ") + ")") if err != nil { - mlog.Critical(fmt.Sprintf("Failed to create index %v", err)) + mlog.Critical("Failed to create index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_CREATE_INDEX_SQLITE) } @@ -845,7 +845,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) + mlog.Critical("Failed to remove index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_POSTGRES) } @@ -855,7 +855,7 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b count, err := ss.GetMaster().SelectInt("SELECT COUNT(0) AS index_exists FROM information_schema.statistics WHERE TABLE_SCHEMA = DATABASE() and table_name = ? AND index_name = ?", tableName, indexName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to check index %v", err)) + mlog.Critical("Failed to check index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_MYSQL) } @@ -866,14 +866,14 @@ func (ss *SqlSupplier) RemoveIndexIfExists(indexName string, tableName string) b _, err = ss.GetMaster().ExecNoTimeout("DROP INDEX " + indexName + " ON " + tableName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) + mlog.Critical("Failed to remove index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_MYSQL) } } else if ss.DriverName() == model.DATABASE_DRIVER_SQLITE { _, err := ss.GetMaster().ExecNoTimeout("DROP INDEX IF EXISTS " + indexName) if err != nil { - mlog.Critical(fmt.Sprintf("Failed to remove index %v", err)) + mlog.Critical("Failed to remove index", mlog.Err(err)) time.Sleep(time.Second) os.Exit(EXIT_REMOVE_INDEX_SQLITE) } From 3c8665f510b5de11764f21c996e8bbaf769c97c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Tue, 10 Sep 2019 11:45:08 +0200 Subject: [PATCH 42/47] Remove premature optimization that was generating a bad users isolation (#12128) * Remove premature optimization that was generating a bad users isolation * fixing tests --- app/user.go | 7 ------- app/user_test.go | 3 ++- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/user.go b/app/user.go index d2566bf5fd..2920eaefa8 100644 --- a/app/user.go +++ b/app/user.go @@ -2151,19 +2151,12 @@ func (a *App) GetViewUsersRestrictions(userId string) (*model.ViewUsersRestricti } teamIdsWithPermission := []string{} - teamIdsWithoutPermission := []string{} for _, teamId := range teamIds { if a.HasPermissionToTeam(userId, teamId, model.PERMISSION_VIEW_MEMBERS) { teamIdsWithPermission = append(teamIdsWithPermission, teamId) - } else { - teamIdsWithoutPermission = append(teamIdsWithoutPermission, teamId) } } - if len(teamIdsWithoutPermission) == 0 { - return &model.ViewUsersRestrictions{Teams: teamIdsWithPermission}, nil - } - userChannelMembers, err := a.Srv.Store.Channel().GetAllChannelMembersForUser(userId, true, true) if err != nil { return nil, err diff --git a/app/user_test.go b/app/user_test.go index f329815221..3f2aac4c06 100644 --- a/app/user_test.go +++ b/app/user_test.go @@ -899,7 +899,8 @@ func TestGetViewUsersRestrictions(t *testing.T) { assert.NotNil(t, restrictions) assert.NotNil(t, restrictions.Teams) - assert.Len(t, restrictions.Channels, 0) + assert.NotNil(t, restrictions.Channels) + assert.ElementsMatch(t, []string{team1townsquare.Id, team1offtopic.Id, team1channel1.Id, team1channel2.Id, team2townsquare.Id, team2offtopic.Id, team2channel1.Id}, restrictions.Channels) assert.ElementsMatch(t, []string{team1.Id, team2.Id}, restrictions.Teams) }) From 2e76c4c0c75ccb8c38dfbe9d4f9f593168ea63c2 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 12:25:16 +0200 Subject: [PATCH 43/47] MM-18261 Converting to structured logging the file app/team.go (#12136) --- app/team.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/team.go b/app/team.go index 688f243524..26da179d14 100644 --- a/app/team.go +++ b/app/team.go @@ -485,7 +485,7 @@ func (a *App) AddUserToTeamByToken(userId string, tokenId string) (*model.Team, for _, channel := range channels { _, err := a.AddUserToChannel(user, channel) if err != nil { - mlog.Error(err.Error()) + mlog.Error("error adding user to channel", mlog.Err(err)) } } } @@ -617,7 +617,12 @@ func (a *App) JoinUserToTeam(team *model.Team, user *model.User, userRequestorId if !user.IsGuest() { // Soft error if there is an issue joining the default channels if err := a.JoinDefaultChannels(team.Id, user, shouldBeAdmin, userRequestorId); err != nil { - mlog.Error(fmt.Sprintf("Encountered an issue joining default channels err=%v", err), mlog.String("user_id", user.Id), mlog.String("team_id", team.Id)) + mlog.Error( + "Encountered an issue joining default channels.", + mlog.String("user_id", user.Id), + mlog.String("team_id", team.Id), + mlog.Err(err), + ) } } @@ -943,11 +948,11 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string) if *a.Config().ServiceSettings.ExperimentalEnableDefaultChannelLeaveJoinMessages { if requestorId == user.Id { if err = a.postLeaveTeamMessage(user, channel); err != nil { - mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) + mlog.Error("Failed to post join/leave message", mlog.Err(err)) } } else { if err = a.postRemoveFromTeamMessage(user, channel); err != nil { - mlog.Error(fmt.Sprint("Failed to post join/leave message", err)) + mlog.Error("Failed to post join/leave message", mlog.Err(err)) } } } @@ -1302,7 +1307,7 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) { return team.Id, nil } // soft fail, so we still create user but don't auto-join team - mlog.Error(fmt.Sprintf("%v", err)) + mlog.Error("error getting team by inviteId.", mlog.String("invite_id", inviteId), mlog.Err(err)) } return "", nil From 94eb3caa732038cb89265ac7047042520c440583 Mon Sep 17 00:00:00 2001 From: Nikhil Ranjan Date: Tue, 10 Sep 2019 14:08:29 +0200 Subject: [PATCH 44/47] Converting to structured logging the file app/command_echo.go (#12144) --- app/command_echo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/command_echo.go b/app/command_echo.go index 2197959114..9b489bda23 100644 --- a/app/command_echo.go +++ b/app/command_echo.go @@ -4,7 +4,6 @@ package app import ( - "fmt" "strconv" "strings" "time" @@ -90,7 +89,7 @@ func (me *EchoProvider) DoCommand(a *App, args *model.CommandArgs, message strin time.Sleep(time.Duration(delay) * time.Second) if _, err := a.CreatePostMissingChannel(post, true); err != nil { - mlog.Error(fmt.Sprintf("Unable to create /echo post, err=%v", err)) + mlog.Error("Unable to create /echo post.", mlog.Err(err)) } }) From 9e6c5e8ea637e5e17da529c9c9c8147c1387d961 Mon Sep 17 00:00:00 2001 From: Lev <1187448+levb@users.noreply.github.com> Date: Tue, 10 Sep 2019 09:28:12 -0700 Subject: [PATCH 45/47] MM-17489: fixed racy tryExecutePluginCommand (#11780) * MM-17489: fixed racy tryExecutePluginCommand * PR feedback * MM 7971 dockerhost -> localhost && docker -> docker-compose (#10872) * replace dockerhost with localhost * remove uneeded setup-max build step (no more dockerhost) * changes as recommended by @cpanato * make clean-docker with docker-compose * added ports to docker-compose.yml (needed for osx). ignore error for ldapadd (when already exists) * add clean-old-docker to legacy.mk * docker-compose stop instead of down for `make stop-docker` * MM-17117: Updates einterface for FirstLoginSync. (#11737) * update sampledata users passwords (#11769) * update sampledata users passwords * update makefile * Tweak docker compose setup (#11785) * fix stray dockerhost mention * fix stray whitespace * fix missing mattermost-redix cleanup in clean-old-docker * absolute path to docker-compose, plus simpler ldap initialization * allow ldapadd to fail on CI * MM-16366 - Prevent attachment duplication on import (#11770) * prevent attachment duplication * improved tests * corrected import into a new post * WIP: a failed attempt to add tests * remove debugging statements * fix godoc * enable plugins when testing * rework reentrant unit tests --- app/command.go | 2 +- app/plugin_commands.go | 31 +++++++---- app/plugin_commands_test.go | 107 +++++++++++++++++++++++++++++++++++- app/plugin_hooks_test.go | 6 ++ 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/app/command.go b/app/command.go index a69d8a3637..0d44b5dd1c 100644 --- a/app/command.go +++ b/app/command.go @@ -187,7 +187,7 @@ func (a *App) ExecuteCommand(args *model.CommandArgs) (*model.CommandResponse, * return nil, model.NewAppError("command", "api.command.execute_command.not_found.app_error", map[string]interface{}{"Trigger": trigger}, "", http.StatusNotFound) } -// tryExecutePluginCommand attempts to run a built in command based on the given arguments. If no such command can be +// tryExecuteBuiltInCommand attempts to run a built in command based on the given arguments. If no such command can be // found, returns nil for all arguments. func (a *App) tryExecuteBuiltInCommand(args *model.CommandArgs, trigger string, message string) (*model.Command, *model.CommandResponse) { provider := GetCommandProvider(trigger) diff --git a/app/plugin_commands.go b/app/plugin_commands.go index 46842451d9..8e1df5fa42 100644 --- a/app/plugin_commands.go +++ b/app/plugin_commands.go @@ -97,20 +97,29 @@ func (a *App) tryExecutePluginCommand(args *model.CommandArgs) (*model.Command, trigger := parts[0][1:] trigger = strings.ToLower(trigger) + var matched *PluginCommand a.Srv.pluginCommandsLock.RLock() - defer a.Srv.pluginCommandsLock.RUnlock() - for _, pc := range a.Srv.pluginCommands { if (pc.Command.TeamId == "" || pc.Command.TeamId == args.TeamId) && pc.Command.Trigger == trigger { - if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { - pluginHooks, err := pluginsEnvironment.HooksForPlugin(pc.PluginId) - if err != nil { - return pc.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError) - } - response, appErr := pluginHooks.ExecuteCommand(a.PluginContext(), args) - return pc.Command, response, appErr - } + matched = pc + break } } - return nil, nil, nil + a.Srv.pluginCommandsLock.RUnlock() + if matched == nil { + return nil, nil, nil + } + + pluginsEnvironment := a.GetPluginsEnvironment() + if pluginsEnvironment == nil { + return nil, nil, nil + } + + pluginHooks, err := pluginsEnvironment.HooksForPlugin(matched.PluginId) + if err != nil { + return matched.Command, nil, model.NewAppError("ExecutePluginCommand", "model.plugin_command.error.app_error", nil, "err="+err.Error(), http.StatusInternalServerError) + } + + response, appErr := pluginHooks.ExecuteCommand(a.PluginContext(), args) + return matched.Command, response, appErr } diff --git a/app/plugin_commands_test.go b/app/plugin_commands_test.go index 830ef48980..06af46d1cd 100644 --- a/app/plugin_commands_test.go +++ b/app/plugin_commands_test.go @@ -5,6 +5,7 @@ package app import ( "testing" + "time" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/utils" @@ -46,7 +47,7 @@ func TestPluginCommand(t *testing.T) { } type MyPlugin struct { - plugin.MattermostPlugin + plugin.MattermostPlugin configuration configuration } @@ -110,6 +111,110 @@ func TestPluginCommand(t *testing.T) { th.App.RemovePlugin(pluginIds[0]) }) + t.Run("re-entrant command registration on config change", func(t *testing.T) { + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.PluginSettings.Plugins["testloadpluginconfig"] = map[string]interface{}{ + "TeamId": args.TeamId, + } + }) + + tearDown, pluginIds, activationErrors := SetAppEnvironmentWithPlugins(t, []string{` + package main + + import ( + "github.com/mattermost/mattermost-server/plugin" + "github.com/mattermost/mattermost-server/model" + ) + + type configuration struct { + TeamId string + } + + type MyPlugin struct { + plugin.MattermostPlugin + + configuration configuration + } + + func (p *MyPlugin) OnConfigurationChange() error { + p.API.LogInfo("OnConfigurationChange") + err := p.API.LoadPluginConfiguration(&p.configuration); + if err != nil { + return err + } + + p.API.LogInfo("About to register") + err = p.API.RegisterCommand(&model.Command{ + TeamId: p.configuration.TeamId, + Trigger: "plugin", + DisplayName: "Plugin Command", + AutoComplete: true, + AutoCompleteDesc: "autocomplete", + }) + if err != nil { + p.API.LogInfo("Registered, with error", err, err.Error()) + return err + } + p.API.LogInfo("Registered, without error") + return nil + } + + func (p *MyPlugin) ExecuteCommand(c *plugin.Context, commandArgs *model.CommandArgs) (*model.CommandResponse, *model.AppError) { + p.API.LogInfo("ExecuteCommand") + // Saving the plugin config eventually results in a call to + // OnConfigurationChange. This used to deadlock on account of + // effectively acquiring a RWLock reentrantly. + err := p.API.SavePluginConfig(map[string]interface{}{ + "TeamId": p.configuration.TeamId, + }) + if err != nil { + p.API.LogError("Failed to save plugin config", err, err.Error()) + return nil, err + } + p.API.LogInfo("ExecuteCommand, saved plugin config") + + return &model.CommandResponse{ + ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, + Text: "text", + }, nil + } + + func main() { + plugin.ClientMain(&MyPlugin{}) + } + `}, th.App, th.App.NewPluginAPI) + defer tearDown() + + require.Len(t, activationErrors, 1) + require.Nil(t, nil, activationErrors[0]) + + wait := make(chan bool) + killed := false + go func() { + defer close(wait) + + resp, err := th.App.ExecuteCommand(args) + + // Ignore if we kill below. + if !killed { + require.Nil(t, err) + require.Equal(t, model.COMMAND_RESPONSE_TYPE_EPHEMERAL, resp.ResponseType) + require.Equal(t, "text", resp.Text) + } + }() + + select { + case <-wait: + case <-time.After(10 * time.Second): + killed = true + } + + th.App.RemovePlugin(pluginIds[0]) + if killed { + t.Fatal("execute command appears to have deadlocked") + } + }) + t.Run("error after plugin command unregistered", func(t *testing.T) { _, err := th.App.ExecuteCommand(args) require.NotNil(t, err) diff --git a/app/plugin_hooks_test.go b/app/plugin_hooks_test.go index 87398ed409..df88b99b9b 100644 --- a/app/plugin_hooks_test.go +++ b/app/plugin_hooks_test.go @@ -46,6 +46,12 @@ func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, a _, _, activationErr := env.Activate(pluginId) pluginIds = append(pluginIds, pluginId) activationErrors = append(activationErrors, activationErr) + + app.UpdateConfig(func(cfg *model.Config) { + cfg.PluginSettings.PluginStates[pluginId] = &model.PluginState{ + Enable: true, + } + }) } return func() { From 1802c575e5e219653eeedb9d91a4b4f70f8f4a08 Mon Sep 17 00:00:00 2001 From: jfrerich Date: Tue, 10 Sep 2019 11:50:27 -0500 Subject: [PATCH 46/47] =?UTF-8?q?[MM-18119]=20Add=20methods=20for=20gettin?= =?UTF-8?q?g=20teams=20and=20count=20when=20query=E2=80=A6=20(#12020)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add methods to handle include_total_count api parameter when permissions for authenticated user is not sysadmin * Add translations for app errors * Add Mocks * Add tests for new methods * When running at the TeamStore testing level, the number of returned teams is different than running tests individually. Fix for now and submit help wanted do proper teardown after each test * correct value when running test at the top level * Add helper function to delete previous teams in db * Instead of checking against numbers of teams returned, check against the actual teams returned. When creating test teams, use unique DisplaName values so the return array will be sorted consistantly. When testing private and public team counts, add teams that should not be counted. Also create odd number of public/private teams for better error protections. Don't want 1 of each type --- api4/team.go | 12 +- api4/team_test.go | 34 ++++- app/team.go | 24 +++ i18n/en.json | 8 + store/sqlstore/team_store.go | 44 ++++++ store/store.go | 3 + store/storetest/mocks/AuditStore.go | 6 +- store/storetest/mocks/BotStore.go | 6 +- .../mocks/ChannelMemberHistoryStore.go | 6 +- store/storetest/mocks/ChannelStore.go | 8 +- .../storetest/mocks/ClusterDiscoveryStore.go | 6 +- store/storetest/mocks/CommandStore.go | 6 +- store/storetest/mocks/CommandWebhookStore.go | 6 +- store/storetest/mocks/ComplianceStore.go | 6 +- store/storetest/mocks/EmojiStore.go | 6 +- store/storetest/mocks/FileInfoStore.go | 6 +- store/storetest/mocks/GroupStore.go | 6 +- store/storetest/mocks/JobStore.go | 6 +- .../mocks/LayeredStoreDatabaseLayer.go | 12 +- store/storetest/mocks/LayeredStoreSupplier.go | 12 +- store/storetest/mocks/LicenseStore.go | 6 +- store/storetest/mocks/LinkMetadataStore.go | 6 +- store/storetest/mocks/OAuthStore.go | 6 +- store/storetest/mocks/PluginStore.go | 6 +- store/storetest/mocks/PostStore.go | 6 +- store/storetest/mocks/PreferenceStore.go | 6 +- store/storetest/mocks/ReactionStore.go | 6 +- store/storetest/mocks/RoleStore.go | 6 +- store/storetest/mocks/SchemeStore.go | 6 +- store/storetest/mocks/SessionStore.go | 6 +- store/storetest/mocks/SqlStore.go | 11 +- store/storetest/mocks/StatusStore.go | 6 +- store/storetest/mocks/Store.go | 6 +- store/storetest/mocks/SystemStore.go | 6 +- store/storetest/mocks/TeamStore.go | 77 +++++++++- store/storetest/mocks/TermsOfServiceStore.go | 6 +- store/storetest/mocks/TokenStore.go | 6 +- store/storetest/mocks/UserAccessTokenStore.go | 6 +- store/storetest/mocks/UserStore.go | 8 +- .../mocks/UserTermsOfServiceStore.go | 6 +- store/storetest/mocks/WebhookStore.go | 6 +- store/storetest/team_store.go | 141 ++++++++++++++++++ 42 files changed, 483 insertions(+), 85 deletions(-) diff --git a/api4/team.go b/api4/team.go index 884e071ec1..5c0df4902c 100644 --- a/api4/team.go +++ b/api4/team.go @@ -740,9 +740,17 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { teams, err = c.App.GetAllTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) } } else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_LIST_PRIVATE_TEAMS) { - teams, err = c.App.GetAllPrivateTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + if c.Params.IncludeTotalCount { + teamsWithCount, err = c.App.GetAllPrivateTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + } else { + teams, err = c.App.GetAllPrivateTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + } } else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_LIST_PUBLIC_TEAMS) { - teams, err = c.App.GetAllPublicTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + if c.Params.IncludeTotalCount { + teamsWithCount, err = c.App.GetAllPublicTeamsPageWithCount(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + } else { + teams, err = c.App.GetAllPublicTeamsPage(c.Params.Page*c.Params.PerPage, c.Params.PerPage) + } } if err != nil { diff --git a/api4/team_test.go b/api4/team_test.go index ff5b0d84eb..dce8f8b7d0 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -621,6 +621,10 @@ func TestGetAllTeams(t *testing.T) { team3, resp = Client.CreateTeam(team3) CheckNoError(t, resp) + team4 := &model.Team{DisplayName: "Name4", Name: GenerateTestTeamName(), Email: th.GenerateTestEmail(), Type: model.TEAM_OPEN, AllowOpenInvite: false} + team4, resp = Client.CreateTeam(team4) + CheckNoError(t, resp) + testCases := []struct { Name string Page int @@ -663,14 +667,14 @@ func TestGetAllTeams(t *testing.T) { Page: 0, PerPage: 10, Permissions: []string{model.PERMISSION_LIST_PRIVATE_TEAMS.Id}, - ExpectedTeams: []string{th.BasicTeam.Id, team3.Id}, + ExpectedTeams: []string{th.BasicTeam.Id, team3.Id, team4.Id}, }, { Name: "Get all teams", Page: 0, PerPage: 10, Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id, model.PERMISSION_LIST_PRIVATE_TEAMS.Id}, - ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id}, + ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id, team4.Id}, }, { Name: "Get no teams because permissions", @@ -684,9 +688,27 @@ func TestGetAllTeams(t *testing.T) { Page: 0, PerPage: 10, Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id, model.PERMISSION_LIST_PRIVATE_TEAMS.Id}, - ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id}, + ExpectedTeams: []string{th.BasicTeam.Id, team1.Id, team2.Id, team3.Id, team4.Id}, WithCount: true, - ExpectedCount: 4, + ExpectedCount: 5, + }, + { + Name: "Get all public teams with count", + Page: 0, + PerPage: 10, + Permissions: []string{model.PERMISSION_LIST_PUBLIC_TEAMS.Id}, + ExpectedTeams: []string{team1.Id, team2.Id}, + WithCount: true, + ExpectedCount: 2, + }, + { + Name: "Get all private teams with count", + Page: 0, + PerPage: 10, + Permissions: []string{model.PERMISSION_LIST_PRIVATE_TEAMS.Id}, + ExpectedTeams: []string{th.BasicTeam.Id, team3.Id, team4.Id}, + WithCount: true, + ExpectedCount: 3, }, } @@ -2310,7 +2332,9 @@ func TestInviteGuestsToTeam(t *testing.T) { defer func() { th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableEmailInvitations = &enableEmailInvitations }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.TeamSettings.RestrictCreationToDomains = restrictCreationToDomains }) - th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.RestrictCreationToDomains = guestRestrictCreationToDomains }) + th.App.UpdateConfig(func(cfg *model.Config) { + cfg.GuestAccountsSettings.RestrictCreationToDomains = guestRestrictCreationToDomains + }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.GuestAccountsSettings.Enable = &enableGuestAccounts }) }() diff --git a/app/team.go b/app/team.go index 26da179d14..3bc3e8409e 100644 --- a/app/team.go +++ b/app/team.go @@ -683,6 +683,18 @@ func (a *App) GetAllPrivateTeamsPage(offset int, limit int) ([]*model.Team, *mod return a.Srv.Store.Team().GetAllPrivateTeamPageListing(offset, limit) } +func (a *App) GetAllPrivateTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { + totalCount, err := a.Srv.Store.Team().AnalyticsPrivateTeamCount() + if err != nil { + return nil, err + } + teams, err := a.Srv.Store.Team().GetAllPrivateTeamPageListing(offset, limit) + if err != nil { + return nil, err + } + return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil +} + func (a *App) GetAllPublicTeams() ([]*model.Team, *model.AppError) { return a.Srv.Store.Team().GetAllTeamListing() } @@ -691,6 +703,18 @@ func (a *App) GetAllPublicTeamsPage(offset int, limit int) ([]*model.Team, *mode return a.Srv.Store.Team().GetAllTeamPageListing(offset, limit) } +func (a *App) GetAllPublicTeamsPageWithCount(offset int, limit int) (*model.TeamsWithCount, *model.AppError) { + totalCount, err := a.Srv.Store.Team().AnalyticsPublicTeamCount() + if err != nil { + return nil, err + } + teams, err := a.Srv.Store.Team().GetAllPublicTeamPageListing(offset, limit) + if err != nil { + return nil, err + } + return &model.TeamsWithCount{Teams: teams, TotalCount: totalCount}, nil +} + func (a *App) SearchAllTeams(term string) ([]*model.Team, *model.AppError) { return a.Srv.Store.Team().SearchAll(term) } diff --git a/i18n/en.json b/i18n/en.json index d2e46f64f3..1901d6bcb6 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6678,6 +6678,14 @@ "id": "store.sql_team.analytics_get_team_count_for_scheme.app_error", "translation": "Unable to get the channel count for the scheme." }, + { + "id": "store.sql_team.analytics_private_team_count.app_error", + "translation": "Unable to count the private teams" + }, + { + "id": "store.sql_team.analytics_public_team_count.app_error", + "translation": "Unable to count the public teams" + }, { "id": "store.sql_team.analytics_team_count.app_error", "translation": "Unable to count the teams" diff --git a/store/sqlstore/team_store.go b/store/sqlstore/team_store.go index 8b4c84ad52..ec3b3770f5 100644 --- a/store/sqlstore/team_store.go +++ b/store/sqlstore/team_store.go @@ -381,6 +381,21 @@ func (s SqlTeamStore) GetAllPrivateTeamListing() ([]*model.Team, *model.AppError return data, nil } +func (s SqlTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) { + query := "SELECT * FROM Teams WHERE AllowOpenInvite = 1 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset" + + if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { + query = "SELECT * FROM Teams WHERE AllowOpenInvite = true ORDER BY DisplayName LIMIT :Limit OFFSET :Offset" + } + + var data []*model.Team + if _, err := s.GetReplica().Select(&data, query, map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil { + return nil, model.NewAppError("SqlTeamStore.GetAllPrivateTeamListing", "store.sql_team.get_all_private_team_listing.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return data, nil +} + func (s SqlTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) { query := "SELECT * FROM Teams WHERE AllowOpenInvite = 0 ORDER BY DisplayName LIMIT :Limit OFFSET :Offset" @@ -433,6 +448,35 @@ func (s SqlTeamStore) PermanentDelete(teamId string) *model.AppError { return nil } +func (s SqlTeamStore) AnalyticsPublicTeamCount() (int64, *model.AppError) { + + c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = 1", map[string]interface{}{}) + + if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { + c, err = s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = true", map[string]interface{}{}) + } + + if err != nil { + return int64(0), model.NewAppError("SqlTeamStore.AnalyticsPublicTeamCount", "store.sql_team.analytics_public_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return c, nil +} + +func (s SqlTeamStore) AnalyticsPrivateTeamCount() (int64, *model.AppError) { + c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = 0", map[string]interface{}{}) + + if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { + c, err = s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0 AND AllowOpenInvite = false", map[string]interface{}{}) + } + + if err != nil { + return int64(0), model.NewAppError("SqlTeamStore.AnalyticsPrivateTeamCount", "store.sql_team.analytics_private_team_count.app_error", nil, err.Error(), http.StatusInternalServerError) + } + + return c, nil +} + func (s SqlTeamStore) AnalyticsTeamCount() (int64, *model.AppError) { c, err := s.GetReplica().SelectInt("SELECT COUNT(*) FROM Teams WHERE DeleteAt = 0", map[string]interface{}{}) diff --git a/store/store.go b/store/store.go index a098317a0b..c6fe2b1d5b 100644 --- a/store/store.go +++ b/store/store.go @@ -70,12 +70,15 @@ type TeamStore interface { GetAllPage(offset int, limit int) ([]*model.Team, *model.AppError) GetAllPrivateTeamListing() ([]*model.Team, *model.AppError) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) + GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) GetAllTeamListing() ([]*model.Team, *model.AppError) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) GetTeamsByUserId(userId string) ([]*model.Team, *model.AppError) GetByInviteId(inviteId string) (*model.Team, *model.AppError) PermanentDelete(teamId string) *model.AppError AnalyticsTeamCount() (int64, *model.AppError) + AnalyticsPublicTeamCount() (int64, *model.AppError) + AnalyticsPrivateTeamCount() (int64, *model.AppError) SaveMember(member *model.TeamMember, maxUsersPerTeam int) (*model.TeamMember, *model.AppError) UpdateMember(member *model.TeamMember) (*model.TeamMember, *model.AppError) GetMember(teamId string, userId string) (*model.TeamMember, *model.AppError) diff --git a/store/storetest/mocks/AuditStore.go b/store/storetest/mocks/AuditStore.go index dd23d9c1ae..7740d16b0a 100644 --- a/store/storetest/mocks/AuditStore.go +++ b/store/storetest/mocks/AuditStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // AuditStore is an autogenerated mock type for the AuditStore type type AuditStore struct { diff --git a/store/storetest/mocks/BotStore.go b/store/storetest/mocks/BotStore.go index d4514ddbaf..c5d6f1d265 100644 --- a/store/storetest/mocks/BotStore.go +++ b/store/storetest/mocks/BotStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // BotStore is an autogenerated mock type for the BotStore type type BotStore struct { diff --git a/store/storetest/mocks/ChannelMemberHistoryStore.go b/store/storetest/mocks/ChannelMemberHistoryStore.go index 9ceac5cbff..0c65677b7b 100644 --- a/store/storetest/mocks/ChannelMemberHistoryStore.go +++ b/store/storetest/mocks/ChannelMemberHistoryStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // ChannelMemberHistoryStore is an autogenerated mock type for the ChannelMemberHistoryStore type type ChannelMemberHistoryStore struct { diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index 86f258a318..8eb871ced0 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -4,9 +4,11 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" -import store "github.com/mattermost/mattermost-server/store" +import ( + model "github.com/mattermost/mattermost-server/model" + store "github.com/mattermost/mattermost-server/store" + mock "github.com/stretchr/testify/mock" +) // ChannelStore is an autogenerated mock type for the ChannelStore type type ChannelStore struct { diff --git a/store/storetest/mocks/ClusterDiscoveryStore.go b/store/storetest/mocks/ClusterDiscoveryStore.go index 7303faba31..1d36295689 100644 --- a/store/storetest/mocks/ClusterDiscoveryStore.go +++ b/store/storetest/mocks/ClusterDiscoveryStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // ClusterDiscoveryStore is an autogenerated mock type for the ClusterDiscoveryStore type type ClusterDiscoveryStore struct { diff --git a/store/storetest/mocks/CommandStore.go b/store/storetest/mocks/CommandStore.go index 5015c3d6c8..a60141d299 100644 --- a/store/storetest/mocks/CommandStore.go +++ b/store/storetest/mocks/CommandStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // CommandStore is an autogenerated mock type for the CommandStore type type CommandStore struct { diff --git a/store/storetest/mocks/CommandWebhookStore.go b/store/storetest/mocks/CommandWebhookStore.go index c388f2386d..b804a0ccd0 100644 --- a/store/storetest/mocks/CommandWebhookStore.go +++ b/store/storetest/mocks/CommandWebhookStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // CommandWebhookStore is an autogenerated mock type for the CommandWebhookStore type type CommandWebhookStore struct { diff --git a/store/storetest/mocks/ComplianceStore.go b/store/storetest/mocks/ComplianceStore.go index b175a9a7ba..e0b4bff3b2 100644 --- a/store/storetest/mocks/ComplianceStore.go +++ b/store/storetest/mocks/ComplianceStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // ComplianceStore is an autogenerated mock type for the ComplianceStore type type ComplianceStore struct { diff --git a/store/storetest/mocks/EmojiStore.go b/store/storetest/mocks/EmojiStore.go index 32cc6a49af..98429c68de 100644 --- a/store/storetest/mocks/EmojiStore.go +++ b/store/storetest/mocks/EmojiStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // EmojiStore is an autogenerated mock type for the EmojiStore type type EmojiStore struct { diff --git a/store/storetest/mocks/FileInfoStore.go b/store/storetest/mocks/FileInfoStore.go index 4a87217349..ef91923a22 100644 --- a/store/storetest/mocks/FileInfoStore.go +++ b/store/storetest/mocks/FileInfoStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // FileInfoStore is an autogenerated mock type for the FileInfoStore type type FileInfoStore struct { diff --git a/store/storetest/mocks/GroupStore.go b/store/storetest/mocks/GroupStore.go index 217bafd399..f630d27581 100644 --- a/store/storetest/mocks/GroupStore.go +++ b/store/storetest/mocks/GroupStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // GroupStore is an autogenerated mock type for the GroupStore type type GroupStore struct { diff --git a/store/storetest/mocks/JobStore.go b/store/storetest/mocks/JobStore.go index 2c9908dece..1a8f34a660 100644 --- a/store/storetest/mocks/JobStore.go +++ b/store/storetest/mocks/JobStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // JobStore is an autogenerated mock type for the JobStore type type JobStore struct { diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go index c5d7691607..cb0ab438f5 100644 --- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go +++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go @@ -4,10 +4,14 @@ package mocks -import context "context" -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" -import store "github.com/mattermost/mattermost-server/store" +import ( + context "context" + + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" + + store "github.com/mattermost/mattermost-server/store" +) // LayeredStoreDatabaseLayer is an autogenerated mock type for the LayeredStoreDatabaseLayer type type LayeredStoreDatabaseLayer struct { diff --git a/store/storetest/mocks/LayeredStoreSupplier.go b/store/storetest/mocks/LayeredStoreSupplier.go index 45682da8f9..6d4e655e22 100644 --- a/store/storetest/mocks/LayeredStoreSupplier.go +++ b/store/storetest/mocks/LayeredStoreSupplier.go @@ -4,10 +4,14 @@ package mocks -import context "context" -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" -import store "github.com/mattermost/mattermost-server/store" +import ( + context "context" + + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" + + store "github.com/mattermost/mattermost-server/store" +) // LayeredStoreSupplier is an autogenerated mock type for the LayeredStoreSupplier type type LayeredStoreSupplier struct { diff --git a/store/storetest/mocks/LicenseStore.go b/store/storetest/mocks/LicenseStore.go index b769b39404..8a376037ef 100644 --- a/store/storetest/mocks/LicenseStore.go +++ b/store/storetest/mocks/LicenseStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // LicenseStore is an autogenerated mock type for the LicenseStore type type LicenseStore struct { diff --git a/store/storetest/mocks/LinkMetadataStore.go b/store/storetest/mocks/LinkMetadataStore.go index 5de2575969..2a7a12cef6 100644 --- a/store/storetest/mocks/LinkMetadataStore.go +++ b/store/storetest/mocks/LinkMetadataStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // LinkMetadataStore is an autogenerated mock type for the LinkMetadataStore type type LinkMetadataStore struct { diff --git a/store/storetest/mocks/OAuthStore.go b/store/storetest/mocks/OAuthStore.go index ab8f97b9ce..3ed44c34b3 100644 --- a/store/storetest/mocks/OAuthStore.go +++ b/store/storetest/mocks/OAuthStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // OAuthStore is an autogenerated mock type for the OAuthStore type type OAuthStore struct { diff --git a/store/storetest/mocks/PluginStore.go b/store/storetest/mocks/PluginStore.go index d618df0ce4..54812a66a6 100644 --- a/store/storetest/mocks/PluginStore.go +++ b/store/storetest/mocks/PluginStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // PluginStore is an autogenerated mock type for the PluginStore type type PluginStore struct { diff --git a/store/storetest/mocks/PostStore.go b/store/storetest/mocks/PostStore.go index 70818dd7d7..acb8adfe7f 100644 --- a/store/storetest/mocks/PostStore.go +++ b/store/storetest/mocks/PostStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // PostStore is an autogenerated mock type for the PostStore type type PostStore struct { diff --git a/store/storetest/mocks/PreferenceStore.go b/store/storetest/mocks/PreferenceStore.go index 293979210a..71828d62b0 100644 --- a/store/storetest/mocks/PreferenceStore.go +++ b/store/storetest/mocks/PreferenceStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // PreferenceStore is an autogenerated mock type for the PreferenceStore type type PreferenceStore struct { diff --git a/store/storetest/mocks/ReactionStore.go b/store/storetest/mocks/ReactionStore.go index 80cb0486c5..41117978bb 100644 --- a/store/storetest/mocks/ReactionStore.go +++ b/store/storetest/mocks/ReactionStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // ReactionStore is an autogenerated mock type for the ReactionStore type type ReactionStore struct { diff --git a/store/storetest/mocks/RoleStore.go b/store/storetest/mocks/RoleStore.go index e19635796c..64254eda52 100644 --- a/store/storetest/mocks/RoleStore.go +++ b/store/storetest/mocks/RoleStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // RoleStore is an autogenerated mock type for the RoleStore type type RoleStore struct { diff --git a/store/storetest/mocks/SchemeStore.go b/store/storetest/mocks/SchemeStore.go index a1e9d0779f..d1c1b1fbb8 100644 --- a/store/storetest/mocks/SchemeStore.go +++ b/store/storetest/mocks/SchemeStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // SchemeStore is an autogenerated mock type for the SchemeStore type type SchemeStore struct { diff --git a/store/storetest/mocks/SessionStore.go b/store/storetest/mocks/SessionStore.go index 4c0a97ecc9..88f3c817d5 100644 --- a/store/storetest/mocks/SessionStore.go +++ b/store/storetest/mocks/SessionStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // SessionStore is an autogenerated mock type for the SessionStore type type SessionStore struct { diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go index 3c7c29c3b1..b4e22062bc 100644 --- a/store/storetest/mocks/SqlStore.go +++ b/store/storetest/mocks/SqlStore.go @@ -4,11 +4,14 @@ package mocks -import gorp "github.com/mattermost/gorp" -import mock "github.com/stretchr/testify/mock" +import ( + gorp "github.com/mattermost/gorp" + mock "github.com/stretchr/testify/mock" -import squirrel "github.com/Masterminds/squirrel" -import store "github.com/mattermost/mattermost-server/store" + squirrel "github.com/Masterminds/squirrel" + + store "github.com/mattermost/mattermost-server/store" +) // SqlStore is an autogenerated mock type for the SqlStore type type SqlStore struct { diff --git a/store/storetest/mocks/StatusStore.go b/store/storetest/mocks/StatusStore.go index 48d4e9a67f..bdfb0b40af 100644 --- a/store/storetest/mocks/StatusStore.go +++ b/store/storetest/mocks/StatusStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // StatusStore is an autogenerated mock type for the StatusStore type type StatusStore struct { diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go index 1fa222c35f..c9b1cb1081 100644 --- a/store/storetest/mocks/Store.go +++ b/store/storetest/mocks/Store.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import store "github.com/mattermost/mattermost-server/store" +import ( + store "github.com/mattermost/mattermost-server/store" + mock "github.com/stretchr/testify/mock" +) // Store is an autogenerated mock type for the Store type type Store struct { diff --git a/store/storetest/mocks/SystemStore.go b/store/storetest/mocks/SystemStore.go index f9eeea7df2..1cd8f54a3e 100644 --- a/store/storetest/mocks/SystemStore.go +++ b/store/storetest/mocks/SystemStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // SystemStore is an autogenerated mock type for the SystemStore type type SystemStore struct { diff --git a/store/storetest/mocks/TeamStore.go b/store/storetest/mocks/TeamStore.go index e490238c37..b5afcea483 100644 --- a/store/storetest/mocks/TeamStore.go +++ b/store/storetest/mocks/TeamStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // TeamStore is an autogenerated mock type for the TeamStore type type TeamStore struct { @@ -35,6 +37,52 @@ func (_m *TeamStore) AnalyticsGetTeamCountForScheme(schemeId string) (int64, *mo return r0, r1 } +// AnalyticsPrivateTeamCount provides a mock function with given fields: +func (_m *TeamStore) AnalyticsPrivateTeamCount() (int64, *model.AppError) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + +// AnalyticsPublicTeamCount provides a mock function with given fields: +func (_m *TeamStore) AnalyticsPublicTeamCount() (int64, *model.AppError) { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func() *model.AppError); ok { + r1 = rf() + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // AnalyticsTeamCount provides a mock function with given fields: func (_m *TeamStore) AnalyticsTeamCount() (int64, *model.AppError) { ret := _m.Called() @@ -252,6 +300,31 @@ func (_m *TeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*mod return r0, r1 } +// GetAllPublicTeamPageListing provides a mock function with given fields: offset, limit +func (_m *TeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, *model.AppError) { + ret := _m.Called(offset, limit) + + var r0 []*model.Team + if rf, ok := ret.Get(0).(func(int, int) []*model.Team); ok { + r0 = rf(offset, limit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Team) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func(int, int) *model.AppError); ok { + r1 = rf(offset, limit) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // GetAllTeamListing provides a mock function with given fields: func (_m *TeamStore) GetAllTeamListing() ([]*model.Team, *model.AppError) { ret := _m.Called() diff --git a/store/storetest/mocks/TermsOfServiceStore.go b/store/storetest/mocks/TermsOfServiceStore.go index fca2d3f367..d107849e98 100644 --- a/store/storetest/mocks/TermsOfServiceStore.go +++ b/store/storetest/mocks/TermsOfServiceStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // TermsOfServiceStore is an autogenerated mock type for the TermsOfServiceStore type type TermsOfServiceStore struct { diff --git a/store/storetest/mocks/TokenStore.go b/store/storetest/mocks/TokenStore.go index b295467f20..a6a22c224f 100644 --- a/store/storetest/mocks/TokenStore.go +++ b/store/storetest/mocks/TokenStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // TokenStore is an autogenerated mock type for the TokenStore type type TokenStore struct { diff --git a/store/storetest/mocks/UserAccessTokenStore.go b/store/storetest/mocks/UserAccessTokenStore.go index 5995688d6a..0a3012f767 100644 --- a/store/storetest/mocks/UserAccessTokenStore.go +++ b/store/storetest/mocks/UserAccessTokenStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // UserAccessTokenStore is an autogenerated mock type for the UserAccessTokenStore type type UserAccessTokenStore struct { diff --git a/store/storetest/mocks/UserStore.go b/store/storetest/mocks/UserStore.go index 017b36cb2c..e901c81d70 100644 --- a/store/storetest/mocks/UserStore.go +++ b/store/storetest/mocks/UserStore.go @@ -4,9 +4,11 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" -import store "github.com/mattermost/mattermost-server/store" +import ( + model "github.com/mattermost/mattermost-server/model" + store "github.com/mattermost/mattermost-server/store" + mock "github.com/stretchr/testify/mock" +) // UserStore is an autogenerated mock type for the UserStore type type UserStore struct { diff --git a/store/storetest/mocks/UserTermsOfServiceStore.go b/store/storetest/mocks/UserTermsOfServiceStore.go index 387abb4c10..3689b7d212 100644 --- a/store/storetest/mocks/UserTermsOfServiceStore.go +++ b/store/storetest/mocks/UserTermsOfServiceStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // UserTermsOfServiceStore is an autogenerated mock type for the UserTermsOfServiceStore type type UserTermsOfServiceStore struct { diff --git a/store/storetest/mocks/WebhookStore.go b/store/storetest/mocks/WebhookStore.go index f6e3a61e4c..2c126cceb0 100644 --- a/store/storetest/mocks/WebhookStore.go +++ b/store/storetest/mocks/WebhookStore.go @@ -4,8 +4,10 @@ package mocks -import mock "github.com/stretchr/testify/mock" -import model "github.com/mattermost/mattermost-server/model" +import ( + model "github.com/mattermost/mattermost-server/model" + mock "github.com/stretchr/testify/mock" +) // WebhookStore is an autogenerated mock type for the WebhookStore type type WebhookStore struct { diff --git a/store/storetest/team_store.go b/store/storetest/team_store.go index edf9fd0f55..8d161cbb66 100644 --- a/store/storetest/team_store.go +++ b/store/storetest/team_store.go @@ -15,6 +15,14 @@ import ( "github.com/mattermost/mattermost-server/store" ) +func cleanupTeamStore(t *testing.T, ss store.Store) { + allTeams, err := ss.Team().GetAll() + for _, team := range allTeams { + ss.Team().PermanentDelete(team.Id) + } + assert.Nil(t, err) +} + func TestTeamStore(t *testing.T, ss store.Store) { createDefaultRoles(t, ss) @@ -31,8 +39,11 @@ func TestTeamStore(t *testing.T, ss store.Store) { t.Run("GetAllTeamPageListing", func(t *testing.T) { testGetAllTeamPageListing(t, ss) }) t.Run("GetAllPrivateTeamListing", func(t *testing.T) { testGetAllPrivateTeamListing(t, ss) }) t.Run("GetAllPrivateTeamPageListing", func(t *testing.T) { testGetAllPrivateTeamPageListing(t, ss) }) + t.Run("GetAllPublicTeamPageListing", func(t *testing.T) { testGetAllPublicTeamPageListing(t, ss) }) t.Run("Delete", func(t *testing.T) { testDelete(t, ss) }) t.Run("TeamCount", func(t *testing.T) { testTeamCount(t, ss) }) + t.Run("TeamPublicCount", func(t *testing.T) { testPublicTeamCount(t, ss) }) + t.Run("TeamPrivateCount", func(t *testing.T) { testPrivateTeamCount(t, ss) }) t.Run("TeamMembers", func(t *testing.T) { testTeamMembers(t, ss) }) t.Run("SaveTeamMemberMaxMembers", func(t *testing.T) { testSaveTeamMemberMaxMembers(t, ss) }) t.Run("GetTeamMember", func(t *testing.T) { testGetTeamMember(t, ss) }) @@ -678,6 +689,66 @@ func testGetAllPrivateTeamPageListing(t *testing.T, ss store.Store) { } } +func testGetAllPublicTeamPageListing(t *testing.T, ss store.Store) { + cleanupTeamStore(t, ss) + + o1 := model.Team{} + o1.DisplayName = "DisplayName1" + o1.Name = "z-z-z" + model.NewId() + "b" + o1.Email = MakeEmail() + o1.Type = model.TEAM_OPEN + o1.AllowOpenInvite = true + t1, err := ss.Team().Save(&o1) + require.Nil(t, err) + + o2 := model.Team{} + o2.DisplayName = "DisplayName2" + o2.Name = "zz" + model.NewId() + "b" + o2.Email = MakeEmail() + o2.Type = model.TEAM_OPEN + o2.AllowOpenInvite = false + _, err = ss.Team().Save(&o2) + require.Nil(t, err) + + o3 := model.Team{} + o3.DisplayName = "DisplayName3" + o3.Name = "z-z-z" + model.NewId() + "b" + o3.Email = MakeEmail() + o3.Type = model.TEAM_INVITE + o3.AllowOpenInvite = true + t3, err := ss.Team().Save(&o3) + require.Nil(t, err) + + o4 := model.Team{} + o4.DisplayName = "DisplayName4" + o4.Name = "zz" + model.NewId() + "b" + o4.Email = MakeEmail() + o4.Type = model.TEAM_INVITE + o4.AllowOpenInvite = false + _, err = ss.Team().Save(&o4) + require.Nil(t, err) + + teams, err := ss.Team().GetAllPublicTeamPageListing(0, 10) + assert.Nil(t, err) + assert.Equal(t, []*model.Team{t1, t3}, teams) + + o5 := model.Team{} + o5.DisplayName = "DisplayName5" + o5.Name = "z-z-z" + model.NewId() + "b" + o5.Email = MakeEmail() + o5.Type = model.TEAM_OPEN + o5.AllowOpenInvite = true + t5, err := ss.Team().Save(&o5) + require.Nil(t, err) + + teams, err = ss.Team().GetAllPublicTeamPageListing(0, 4) + assert.Nil(t, err) + assert.Equal(t, []*model.Team{t1, t3, t5}, teams) + + teams, err = ss.Team().GetAllPublicTeamPageListing(1, 1) + assert.Nil(t, err) +} + func testDelete(t *testing.T, ss store.Store) { o1 := model.Team{} o1.DisplayName = "DisplayName" @@ -701,6 +772,76 @@ func testDelete(t *testing.T, ss store.Store) { } } +func testPublicTeamCount(t *testing.T, ss store.Store) { + cleanupTeamStore(t, ss) + + o1 := model.Team{} + o1.DisplayName = "DisplayName" + o1.Name = "z-z-z" + model.NewId() + "b" + o1.Email = MakeEmail() + o1.Type = model.TEAM_OPEN + o1.AllowOpenInvite = true + _, err := ss.Team().Save(&o1) + require.Nil(t, err) + + o2 := model.Team{} + o2.DisplayName = "DisplayName" + o2.Name = "z-z-z" + model.NewId() + "b" + o2.Email = MakeEmail() + o2.Type = model.TEAM_OPEN + o2.AllowOpenInvite = false + _, err = ss.Team().Save(&o2) + require.Nil(t, err) + + o3 := model.Team{} + o3.DisplayName = "DisplayName" + o3.Name = "z-z-z" + model.NewId() + "b" + o3.Email = MakeEmail() + o3.Type = model.TEAM_OPEN + o3.AllowOpenInvite = true + _, err = ss.Team().Save(&o3) + require.Nil(t, err) + + teamCount, err := ss.Team().AnalyticsPublicTeamCount() + require.Nil(t, err) + require.Equal(t, int64(2), teamCount, "should only be 1 team") +} + +func testPrivateTeamCount(t *testing.T, ss store.Store) { + cleanupTeamStore(t, ss) + + o1 := model.Team{} + o1.DisplayName = "DisplayName" + o1.Name = "z-z-z" + model.NewId() + "b" + o1.Email = MakeEmail() + o1.Type = model.TEAM_OPEN + o1.AllowOpenInvite = false + _, err := ss.Team().Save(&o1) + require.Nil(t, err) + + o2 := model.Team{} + o2.DisplayName = "DisplayName" + o2.Name = "z-z-z" + model.NewId() + "b" + o2.Email = MakeEmail() + o2.Type = model.TEAM_OPEN + o2.AllowOpenInvite = true + _, err = ss.Team().Save(&o2) + require.Nil(t, err) + + o3 := model.Team{} + o3.DisplayName = "DisplayName" + o3.Name = "z-z-z" + model.NewId() + "b" + o3.Email = MakeEmail() + o3.Type = model.TEAM_OPEN + o3.AllowOpenInvite = false + _, err = ss.Team().Save(&o3) + require.Nil(t, err) + + teamCount, err := ss.Team().AnalyticsPrivateTeamCount() + require.Nil(t, err) + require.Equal(t, int64(2), teamCount, "should only be 1 team") +} + func testTeamCount(t *testing.T, ss store.Store) { o1 := model.Team{} o1.DisplayName = "DisplayName" From 63552416fb775520100f5071941207ca9090a5ca Mon Sep 17 00:00:00 2001 From: sowmiyamuthuraman <32141844+sowmiyamuthuraman@users.noreply.github.com> Date: Wed, 11 Sep 2019 00:50:35 +0530 Subject: [PATCH 47/47] Fix unable to access 'https://git.apache.org/thrift.git/' (#12146) --- go.mod | 7 +++++-- go.sum | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index be78cdd841..9d678fef9e 100644 --- a/go.mod +++ b/go.mod @@ -100,5 +100,8 @@ require ( willnorris.com/go/imageproxy v0.9.0 ) -// Workaround for https://github.com/golang/go/issues/30831 and fallout. -replace github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 +replace ( + git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999 + // Workaround for https://github.com/golang/go/issues/30831 and fallout. + github.com/golang/lint => github.com/golang/lint v0.0.0-20190227174305-8f45f776aaf1 +) diff --git a/go.sum b/go.sum index d99bd9a686..ad84326b89 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok= contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=