Merge branch 'master' into mark-as-unread

Этот коммит содержится в:
Harrison Healey
2019-09-10 15:34:29 -04:00
родитель 3629f26bb9 63552416fb
Коммит 42e927cc3f
197 изменённых файлов: 5210 добавлений и 686 удалений

8
.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."

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

@@ -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))))
@@ -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
@@ -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
@@ -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

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

@@ -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 {

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

@@ -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()))
}

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

@@ -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", "")

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

@@ -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)

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

@@ -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()

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

@@ -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
@@ -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 {

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

@@ -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 })
}()

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

@@ -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")

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

@@ -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()

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

@@ -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)

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

@@ -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)
}
}

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

@@ -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
}
@@ -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)
}
@@ -1720,12 +1724,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
}

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

@@ -1042,6 +1042,21 @@ func TestSearchChannelsForUser(t *testing.T) {
})
}
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)
}
func TestMarkChannelAsUnreadFromPost(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()

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

@@ -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)

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

@@ -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))
}
})

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

@@ -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)

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

@@ -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)
}

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

@@ -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),

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

@@ -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()

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

@@ -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)
}
}
}
@@ -784,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
}

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

@@ -6,6 +6,7 @@ package app
import (
"fmt"
"html"
"html/template"
"net/url"
"path/filepath"
"strings"
@@ -73,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)
@@ -171,7 +167,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 +282,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("<a href='%s'>%s</a>", 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

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

@@ -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"
@@ -25,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)
}
@@ -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 := "<b>Bold Test</b>"
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, "<a href='"+channelURL+"'>"+mention+"</a>")
}
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: <a href='%s'>%s</a>;"+
" Channel2: <a href='%s'>%s</a>; Channel3: <a href='%s'>%s</a>",
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, "<a href='"+channelURL+"'>"+mention+"</a>")
}
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+"<a href='"+channelURL+"'>"+mention+"</a>", 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: <a href='%s'>%s</a>;"+
" Channel2: <a href='%s'>%s</a>; Channel3: <a href='%s'>%s</a>",
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)
}

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

@@ -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
}
@@ -151,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)
@@ -475,3 +428,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
}

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

@@ -4,6 +4,7 @@
package app
import (
"fmt"
"testing"
"github.com/mattermost/mattermost-server/model"
@@ -880,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,
@@ -890,9 +891,59 @@ 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)
})
}
}
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)
})
}
}

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

@@ -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) {
@@ -1743,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))
})
}

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

@@ -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)
}
})
}

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

@@ -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)
}

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

@@ -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)
}
}

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

@@ -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
}

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

@@ -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)

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

@@ -11,7 +11,6 @@ import (
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
@@ -47,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() {
@@ -172,9 +177,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 +221,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 +287,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 +331,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 +370,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 +418,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 +571,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 +667,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 +696,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 +705,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 +735,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 +745,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 +776,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 +828,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 +957,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)
}

69
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")
}
}

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

@@ -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)
}
@@ -482,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))
}
}
}
@@ -614,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),
)
}
}
@@ -675,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()
}
@@ -683,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)
}
@@ -940,11 +972,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))
}
}
}
@@ -1299,7 +1331,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

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

@@ -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) {

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

@@ -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

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

@@ -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)
})

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

@@ -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) {

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

@@ -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")

78
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
}

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

@@ -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

9
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
@@ -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
)

24
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=
@@ -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=

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

@@ -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"
@@ -3424,7 +3428,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",
@@ -5662,6 +5666,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"
@@ -6678,6 +6686,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"

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

@@ -103,8 +103,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 +147,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 +157,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", fmt.Sprintf("%v", schedulers.nextRunTimes[idx])))
}
func (schedulers *Schedulers) scheduleJob(cfg *model.Config, scheduler model.Scheduler) (*model.Job, *model.AppError) {

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

@@ -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 {

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

@@ -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")
}
@@ -2922,6 +2926,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())

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

@@ -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/"
@@ -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 {

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

@@ -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("")

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

@@ -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",

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

@@ -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

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

@@ -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.

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

@@ -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
}

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

@@ -284,16 +284,46 @@ 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()
}
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

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

@@ -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
}

111
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
}

297
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)
})
})
}

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

@@ -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
}

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

@@ -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)
})
}

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

@@ -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)

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

@@ -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 {

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

@@ -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 {

53
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

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

@@ -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"

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

@@ -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<html><body>" + htmlBody + "</body></html>"
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 = ""
}

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

@@ -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
}

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

@@ -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 {

514
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)
}

1539
store/sqlstore/integrity_test.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
}
}

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

@@ -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)
}
@@ -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) {

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

@@ -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))
}
}

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

@@ -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{}{})

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

@@ -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) {

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

@@ -55,6 +55,7 @@ type Store interface {
TotalMasterDbConnections() int
TotalReadDbConnections() int
TotalSearchDbConnections() int
CheckIntegrity() <-chan IntegrityCheckResult
}
type TeamStore interface {
@@ -69,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)
@@ -147,6 +151,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)
@@ -628,3 +635,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
}

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

@@ -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(),

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {
@@ -993,6 +995,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 +1178,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)

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {
@@ -78,6 +82,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()

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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

@@ -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 {

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