diff options
-rw-r--r-- | api/apitestlib.go | 13 | ||||
-rw-r--r-- | api/post_test.go | 10 | ||||
-rw-r--r-- | api/team.go | 24 | ||||
-rw-r--r-- | api/team_test.go | 357 | ||||
-rw-r--r-- | api4/post_test.go | 14 | ||||
-rw-r--r-- | api4/team.go | 16 | ||||
-rw-r--r-- | api4/team_test.go | 469 | ||||
-rw-r--r-- | app/command.go | 5 | ||||
-rw-r--r-- | app/command_test.go | 20 | ||||
-rw-r--r-- | app/post.go | 19 | ||||
-rw-r--r-- | app/team.go | 26 | ||||
-rw-r--r-- | app/team_test.go | 214 | ||||
-rw-r--r-- | app/webhook.go | 5 | ||||
-rw-r--r-- | app/webhook_test.go | 5 | ||||
-rw-r--r-- | i18n/de.json | 100 | ||||
-rw-r--r-- | i18n/en.json | 16 | ||||
-rw-r--r-- | i18n/es.json | 10 | ||||
-rw-r--r-- | i18n/fr.json | 8 | ||||
-rw-r--r-- | i18n/it.json | 10 | ||||
-rw-r--r-- | i18n/ja.json | 10 | ||||
-rw-r--r-- | i18n/ko.json | 8 | ||||
-rw-r--r-- | i18n/nl.json | 8 | ||||
-rw-r--r-- | i18n/pl.json | 74 | ||||
-rw-r--r-- | i18n/pt-BR.json | 10 | ||||
-rw-r--r-- | i18n/ru.json | 8 | ||||
-rw-r--r-- | i18n/tr.json | 10 | ||||
-rw-r--r-- | i18n/zh-CN.json | 42 | ||||
-rw-r--r-- | i18n/zh-TW.json | 52 | ||||
-rw-r--r-- | model/client.go | 2 | ||||
-rw-r--r-- | utils/config.go | 12 | ||||
-rw-r--r-- | utils/mail.go | 24 |
31 files changed, 1415 insertions, 186 deletions
diff --git a/api/apitestlib.go b/api/apitestlib.go index 5b5bfff19..58e2a5965 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -6,6 +6,7 @@ package api import ( "fmt" "net" + "strings" "time" "github.com/mattermost/mattermost-server/api4" @@ -180,8 +181,8 @@ func (me *TestHelper) CreateTeam(client *model.Client) *model.Team { id := model.NewId() team := &model.Team{ DisplayName: "dn_" + id, - Name: "name" + id, - Email: "success+" + id + "@simulator.amazonses.com", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), Type: model.TEAM_OPEN, } @@ -358,6 +359,14 @@ func (me *TestHelper) LoginSystemAdmin() { utils.EnableDebugLogForTest() } +func GenerateTestEmail() string { + return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com") +} + +func GenerateTestTeamName() string { + return "faketeam" + model.NewRandomString(6) +} + func (me *TestHelper) TearDown() { me.App.Shutdown() if err := recover(); err != nil { diff --git a/api/post_test.go b/api/post_test.go index e7c230963..58a9fae73 100644 --- a/api/post_test.go +++ b/api/post_test.go @@ -61,6 +61,11 @@ func TestCreatePost(t *testing.T) { t.Fatal("Newly craeted post shouldn't have EditAt set") } + _, err = Client.CreatePost(&model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a", Type: model.POST_SYSTEM_GENERIC}) + if err == nil { + t.Fatal("should have failed - bad post type") + } + post2 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", RootId: rpost1.Data.(*model.Post).Id} rpost2, err := Client.CreatePost(post2) if err != nil { @@ -454,13 +459,12 @@ func TestUpdatePost(t *testing.T) { } } - post3 := &model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE} - rpost3, err := Client.CreatePost(post3) + rpost3, err := th.App.CreatePost(&model.Post{ChannelId: channel1.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE, UserId: th.BasicUser.Id}, channel1, false) if err != nil { t.Fatal(err) } - up3 := &model.Post{Id: rpost3.Data.(*model.Post).Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " update post 3"} + up3 := &model.Post{Id: rpost3.Id, ChannelId: channel1.Id, Message: "zz" + model.NewId() + " update post 3"} if _, err := Client.UpdatePost(up3); err == nil { t.Fatal("shouldn't have been able to update system message") } diff --git a/api/team.go b/api/team.go index 8a8d3c935..49b20686d 100644 --- a/api/team.go +++ b/api/team.go @@ -67,6 +67,8 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Don't sanitize the team here since the user will be a team admin and their session won't reflect that yet + w.Write([]byte(rteam.ToJson())) } @@ -82,11 +84,10 @@ func GetAllTeamListings(c *Context, w http.ResponseWriter, r *http.Request) { m := make(map[string]*model.Team) for _, v := range teams { m[v.Id] = v - if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) { - m[v.Id].Sanitize() - } } + sanitizeTeamMap(c.Session, m) + w.Write([]byte(model.TeamMapToJson(m))) } @@ -112,6 +113,8 @@ func getAll(c *Context, w http.ResponseWriter, r *http.Request) { m[v.Id] = v } + sanitizeTeamMap(c.Session, m) + w.Write([]byte(model.TeamMapToJson(m))) } @@ -207,7 +210,7 @@ func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) return } - team.Sanitize() + app.SanitizeTeam(c.Session, team) w.Write([]byte(team.ToJson())) } @@ -241,6 +244,8 @@ func getTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { } } + app.SanitizeTeam(c.Session, team) + w.Write([]byte(team.ToJson())) return } @@ -294,6 +299,8 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeam(c.Session, updatedTeam) + w.Write([]byte(updatedTeam.ToJson())) } @@ -342,6 +349,9 @@ func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } else { w.Header().Set(model.HEADER_ETAG_SERVER, team.Etag()) + + app.SanitizeTeam(c.Session, team) + w.Write([]byte(team.ToJson())) return } @@ -529,3 +539,9 @@ func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) { return } } + +func sanitizeTeamMap(session model.Session, teams map[string]*model.Team) { + for _, team := range teams { + app.SanitizeTeam(session, team) + } +} diff --git a/api/team_test.go b/api/team_test.go index ea29b9d6f..1e4b36433 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -56,6 +56,49 @@ func TestCreateTeam(t *testing.T) { } } +func TestCreateTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + // Non-admin users can create a team, but they become a team admin by doing so + + t.Run("team admin", func(t *testing.T) { + team := &model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + } + + if res, err := th.BasicClient.CreateTeam(team); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + team := &model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + } + + if res, err := th.SystemAdminClient.CreateTeam(team); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestAddUserToTeam(t *testing.T) { th := Setup().InitSystemAdmin().InitBasic() defer th.TearDown() @@ -253,6 +296,77 @@ func TestGetAllTeams(t *testing.T) { } } +func TestGetAllTeamsSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + var team *model.Team + if res, err := th.BasicClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }); err != nil { + t.Fatal(err) + } else { + team = res.Data.(*model.Team) + } + + var team2 *model.Team + if res, err := th.SystemAdminClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }); err != nil { + t.Fatal(err) + } else { + team2 = res.Data.(*model.Team) + } + + t.Run("team admin/team user", func(t *testing.T) { + if res, err := th.BasicClient.GetAllTeams(); err != nil { + t.Fatal(err) + } else { + for _, rteam := range res.Data.(map[string]*model.Team) { + if rteam.Id == team.Id { + if rteam.Email == "" { + t.Fatal("should not have sanitized email for team admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains for team admin") + } + } else if rteam.Id == team2.Id { + if rteam.Email != "" { + t.Fatal("should've sanitized email for non-admin") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains for non-admin") + } + } + } + } + }) + + t.Run("system admin", func(t *testing.T) { + if res, err := th.SystemAdminClient.GetAllTeams(); err != nil { + t.Fatal(err) + } else { + for _, rteam := range res.Data.(map[string]*model.Team) { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + } + }) +} + func TestGetAllTeamListings(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() @@ -277,10 +391,7 @@ func TestGetAllTeamListings(t *testing.T) { } else { teams := r1.Data.(map[string]*model.Team) if teams[team.Id].Name != team.Name { - t.Fatal() - } - if teams[team.Id].Email != "" { - t.Fatal("Non admin users shoudn't get full listings") + t.Fatal("team name doesn't match") } } @@ -294,14 +405,84 @@ func TestGetAllTeamListings(t *testing.T) { } else { teams := r1.Data.(map[string]*model.Team) if teams[team.Id].Name != team.Name { - t.Fatal() - } - if teams[team.Id].Email != team.Email { - t.Fatal() + t.Fatal("team name doesn't match") } } } +func TestGetAllTeamListingsSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + var team *model.Team + if res, err := th.BasicClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + AllowOpenInvite: true, + }); err != nil { + t.Fatal(err) + } else { + team = res.Data.(*model.Team) + } + + var team2 *model.Team + if res, err := th.SystemAdminClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + AllowOpenInvite: true, + }); err != nil { + t.Fatal(err) + } else { + team2 = res.Data.(*model.Team) + } + + t.Run("team admin/non-admin", func(t *testing.T) { + if res, err := th.BasicClient.GetAllTeamListings(); err != nil { + t.Fatal(err) + } else { + for _, rteam := range res.Data.(map[string]*model.Team) { + if rteam.Id == team.Id { + if rteam.Email == "" { + t.Fatal("should not have sanitized email for team admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains for team admin") + } + } else if rteam.Id == team2.Id { + if rteam.Email != "" { + t.Fatal("should've sanitized email for non-admin") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains for non-admin") + } + } + } + } + }) + + t.Run("system admin", func(t *testing.T) { + if res, err := th.SystemAdminClient.GetAllTeamListings(); err != nil { + t.Fatal(err) + } else { + for _, rteam := range res.Data.(map[string]*model.Team) { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + } + }) +} + func TestTeamPermDelete(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() @@ -476,6 +657,52 @@ func TestUpdateTeamDisplayName(t *testing.T) { } } +func TestUpdateTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + var team *model.Team + if res, err := th.BasicClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }); err != nil { + t.Fatal(err) + } else { + team = res.Data.(*model.Team) + } + + // Non-admin users cannot update the team + + t.Run("team admin", func(t *testing.T) { + // API v3 always assumes you're updating the current team + th.BasicClient.SetTeamId(team.Id) + + if res, err := th.BasicClient.UpdateTeam(team); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + // API v3 always assumes you're updating the current team + th.SystemAdminClient.SetTeamId(team.Id) + + if res, err := th.SystemAdminClient.UpdateTeam(team); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestFuzzyTeamCreate(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() @@ -537,6 +764,65 @@ func TestGetMyTeam(t *testing.T) { } } +func TestGetMyTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + var team *model.Team + if res, err := th.BasicClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }); err != nil { + t.Fatal(err) + } else { + team = res.Data.(*model.Team) + } + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + + client := th.CreateClient() + client.Must(client.Login(th.BasicUser2.Email, th.BasicUser2.Password)) + + client.SetTeamId(team.Id) + + if res, err := client.GetMyTeam(""); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + }) + + t.Run("team admin", func(t *testing.T) { + th.BasicClient.SetTeamId(team.Id) + + if res, err := th.BasicClient.GetMyTeam(""); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + th.SystemAdminClient.SetTeamId(team.Id) + + if res, err := th.SystemAdminClient.GetMyTeam(""); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestGetTeamMembers(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() @@ -898,6 +1184,61 @@ func TestGetTeamByName(t *testing.T) { } } +func TestGetTeamByNameSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + var team *model.Team + if res, err := th.BasicClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }); err != nil { + t.Fatal(err) + } else { + team = res.Data.(*model.Team) + } + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + + client := th.CreateClient() + client.Must(client.Login(th.BasicUser2.Email, th.BasicUser2.Password)) + + if res, err := client.GetTeamByName(team.Name); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + }) + + t.Run("team admin", func(t *testing.T) { + if res, err := th.BasicClient.GetTeamByName(team.Name); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + th.SystemAdminClient.SetTeamId(team.Id) + + if res, err := th.SystemAdminClient.GetTeamByName(team.Name); err != nil { + t.Fatal(err) + } else if rteam := res.Data.(*model.Team); rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestFindTeamByName(t *testing.T) { th := Setup().InitBasic() defer th.TearDown() diff --git a/api4/post_test.go b/api4/post_test.go index fd6f8031d..27e6d6458 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -66,6 +66,13 @@ func TestCreatePost(t *testing.T) { t.Fatal("create at should not match") } + post.RootId = "" + post.ParentId = "" + post.Type = model.POST_SYSTEM_GENERIC + _, resp = Client.CreatePost(post) + CheckBadRequestStatus(t, resp) + + post.Type = "" post.RootId = rpost2.Id post.ParentId = rpost2.Id _, resp = Client.CreatePost(post) @@ -417,9 +424,10 @@ func TestUpdatePost(t *testing.T) { t.Fatal("failed to updates") } - post2 := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE} - rpost2, resp := Client.CreatePost(post2) - CheckNoError(t, resp) + rpost2, err := th.App.CreatePost(&model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE, UserId: th.BasicUser.Id}, channel, false) + if err != nil { + t.Fatal(err) + } up2 := &model.Post{Id: rpost2.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + " update post 2"} _, resp = Client.UpdatePost(rpost2.Id, up2) diff --git a/api4/team.go b/api4/team.go index a94da2bef..2c60d40a1 100644 --- a/api4/team.go +++ b/api4/team.go @@ -71,6 +71,8 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + // Don't sanitize the team here since the user will be a team admin and their session won't reflect that yet + w.WriteHeader(http.StatusCreated) w.Write([]byte(rteam.ToJson())) } @@ -90,6 +92,8 @@ func getTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeam(c.Session, team) + w.Write([]byte(team.ToJson())) return } @@ -110,6 +114,8 @@ func getTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeam(c.Session, team) + w.Write([]byte(team.ToJson())) return } @@ -142,6 +148,8 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeam(c.Session, updatedTeam) + w.Write([]byte(updatedTeam.ToJson())) } @@ -170,6 +178,8 @@ func patchTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeam(c.Session, patchedTeam) + c.LogAudit("") w.Write([]byte(patchedTeam.ToJson())) } @@ -215,6 +225,8 @@ func getTeamsForUser(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = err return } else { + app.SanitizeTeams(c.Session, teams) + w.Write([]byte(model.TeamListToJson(teams))) } } @@ -541,6 +553,8 @@ func getAllTeams(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeams(c.Session, teams) + w.Write([]byte(model.TeamListToJson(teams))) } @@ -570,6 +584,8 @@ func searchTeams(c *Context, w http.ResponseWriter, r *http.Request) { return } + app.SanitizeTeams(c.Session, teams) + w.Write([]byte(model.TeamListToJson(teams))) } diff --git a/api4/team_test.go b/api4/team_test.go index bd42682bf..45484e2a1 100644 --- a/api4/team_test.go +++ b/api4/team_test.go @@ -7,7 +7,6 @@ import ( "encoding/binary" "fmt" "net/http" - "reflect" "strconv" "strings" "testing" @@ -82,6 +81,49 @@ func TestCreateTeam(t *testing.T) { CheckForbiddenStatus(t, resp) } +func TestCreateTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + // Non-admin users can create a team, but they become a team admin by doing so + + t.Run("team admin", func(t *testing.T) { + team := &model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + } + + rteam, resp := th.Client.CreateTeam(team) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + team := &model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + } + + rteam, resp := th.SystemAdminClient.CreateTeam(team) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestGetTeam(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -129,6 +171,55 @@ func TestGetTeam(t *testing.T) { CheckNoError(t, resp) } +func TestGetTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + + client := th.CreateClient() + th.LoginBasic2WithClient(client) + + rteam, resp := client.GetTeam(team.Id, "") + CheckNoError(t, resp) + if rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + }) + + t.Run("team admin", func(t *testing.T) { + rteam, resp := th.Client.GetTeam(team.Id, "") + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + rteam, resp := th.SystemAdminClient.GetTeam(team.Id, "") + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestGetTeamUnread(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -203,6 +294,14 @@ func TestUpdateTeam(t *testing.T) { t.Fatal("Update failed") } + team.AllowedDomains = "domain" + uteam, resp = Client.UpdateTeam(team) + CheckNoError(t, resp) + + if uteam.AllowedDomains != "domain" { + t.Fatal("Update failed") + } + team.Name = "Updated name" uteam, resp = Client.UpdateTeam(team) CheckNoError(t, resp) @@ -227,14 +326,6 @@ func TestUpdateTeam(t *testing.T) { t.Fatal("Should not update type") } - team.AllowedDomains = "domain" - uteam, resp = Client.UpdateTeam(team) - CheckNoError(t, resp) - - if uteam.AllowedDomains == "domain" { - t.Fatal("Should not update allowed_domains") - } - originalTeamId := team.Id team.Id = model.NewId() @@ -261,6 +352,42 @@ func TestUpdateTeam(t *testing.T) { CheckNoError(t, resp) } +func TestUpdateTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + // Non-admin users cannot update the team + + t.Run("team admin", func(t *testing.T) { + rteam, resp := th.Client.UpdateTeam(team) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + rteam, resp := th.SystemAdminClient.UpdateTeam(team) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestPatchTeam(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -284,7 +411,6 @@ func TestPatchTeam(t *testing.T) { rteam, resp := Client.PatchTeam(team.Id, patch) CheckNoError(t, resp) - CheckTeamSanitization(t, rteam) if rteam.DisplayName != "Other name" { t.Fatal("DisplayName did not update properly") @@ -330,6 +456,42 @@ func TestPatchTeam(t *testing.T) { CheckNoError(t, resp) } +func TestPatchTeamSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + // Non-admin users cannot update the team + + t.Run("team admin", func(t *testing.T) { + rteam, resp := th.Client.PatchTeam(team.Id, &model.TeamPatch{}) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + rteam, resp := th.SystemAdminClient.PatchTeam(team.Id, &model.TeamPatch{}) + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email for admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestSoftDeleteTeam(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -463,6 +625,77 @@ func TestGetAllTeams(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestGetAllTeamsSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + AllowOpenInvite: true, + }) + CheckNoError(t, resp) + team2, resp := th.SystemAdminClient.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + AllowOpenInvite: true, + }) + CheckNoError(t, resp) + + // This may not work if the server has over 1000 open teams on it + + t.Run("team admin/non-admin", func(t *testing.T) { + teamFound := false + team2Found := false + + rteams, resp := th.Client.GetAllTeams("", 0, 1000) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id == team.Id { + teamFound = true + if rteam.Email == "" { + t.Fatal("should not have sanitized email for team admin") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains for team admin") + } + } else if rteam.Id == team2.Id { + team2Found = true + if rteam.Email != "" { + t.Fatal("should've sanitized email for non-admin") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains for non-admin") + } + } + } + + if !teamFound || !team2Found { + t.Fatal("wasn't returned the expected teams so the test wasn't run correctly") + } + }) + + t.Run("system admin", func(t *testing.T) { + rteams, resp := th.SystemAdminClient.GetAllTeams("", 0, 1000) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + }) +} + func TestGetTeamByName(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -507,6 +740,55 @@ func TestGetTeamByName(t *testing.T) { CheckForbiddenStatus(t, resp) } +func TestGetTeamByNameSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + + client := th.CreateClient() + th.LoginBasic2WithClient(client) + + rteam, resp := client.GetTeamByName(team.Name, "") + CheckNoError(t, resp) + if rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + }) + + t.Run("team admin/non-admin", func(t *testing.T) { + rteam, resp := th.Client.GetTeamByName(team.Name, "") + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) + + t.Run("system admin", func(t *testing.T) { + rteam, resp := th.SystemAdminClient.GetTeamByName(team.Name, "") + CheckNoError(t, resp) + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + }) +} + func TestSearchAllTeams(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -514,8 +796,11 @@ func TestSearchAllTeams(t *testing.T) { oTeam := th.BasicTeam oTeam.AllowOpenInvite = true - updatedTeam, _ := th.App.UpdateTeam(oTeam) - oTeam.UpdateAt = updatedTeam.UpdateAt + if updatedTeam, err := th.App.UpdateTeam(oTeam); err != nil { + t.Fatal(err) + } else { + oTeam.UpdateAt = updatedTeam.UpdateAt + } pTeam := &model.Team{DisplayName: "PName", Name: GenerateTestTeamName(), Email: GenerateTestEmail(), Type: model.TEAM_INVITE} Client.CreateTeam(pTeam) @@ -527,7 +812,7 @@ func TestSearchAllTeams(t *testing.T) { t.Fatal("should have returned 1 team") } - if !reflect.DeepEqual(rteams[0], oTeam) { + if oTeam.Id != rteams[0].Id { t.Fatal("invalid team") } @@ -538,7 +823,7 @@ func TestSearchAllTeams(t *testing.T) { t.Fatal("should have returned 1 team") } - if !reflect.DeepEqual(rteams[0], oTeam) { + if rteams[0].Id != oTeam.Id { t.Fatal("invalid team") } @@ -586,6 +871,86 @@ func TestSearchAllTeams(t *testing.T) { CheckUnauthorizedStatus(t, resp) } +func TestSearchAllTeamsSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + team2, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + t.Run("non-team user", func(t *testing.T) { + client := th.CreateClient() + th.LoginBasic2WithClient(client) + + rteams, resp := client.SearchTeams(&model.TeamSearch{Term: t.Name()}) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + } + }) + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + + client := th.CreateClient() + th.LoginBasic2WithClient(client) + + rteams, resp := client.SearchTeams(&model.TeamSearch{Term: t.Name()}) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + } + }) + + t.Run("team admin", func(t *testing.T) { + rteams, resp := th.Client.SearchTeams(&model.TeamSearch{Term: t.Name()}) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id == team.Id || rteam.Id == team2.Id || rteam.Id == th.BasicTeam.Id { + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + } + }) + + t.Run("system admin", func(t *testing.T) { + rteams, resp := th.SystemAdminClient.SearchTeams(&model.TeamSearch{Term: t.Name()}) + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + }) +} + func TestGetTeamsForUser(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() @@ -628,6 +993,82 @@ func TestGetTeamsForUser(t *testing.T) { CheckNoError(t, resp) } +func TestGetTeamsForUserSanitization(t *testing.T) { + th := Setup().InitBasic().InitSystemAdmin() + defer th.TearDown() + + team, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_1", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + team2, resp := th.Client.CreateTeam(&model.Team{ + DisplayName: t.Name() + "_2", + Name: GenerateTestTeamName(), + Email: GenerateTestEmail(), + Type: model.TEAM_OPEN, + AllowedDomains: "simulator.amazonses.com", + }) + CheckNoError(t, resp) + + t.Run("team user", func(t *testing.T) { + th.LinkUserToTeam(th.BasicUser2, team) + th.LinkUserToTeam(th.BasicUser2, team2) + + client := th.CreateClient() + th.LoginBasic2WithClient(client) + + rteams, resp := client.GetTeamsForUser(th.BasicUser2.Id, "") + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email != "" { + t.Fatal("should've sanitized email") + } else if rteam.AllowedDomains != "" { + t.Fatal("should've sanitized allowed domains") + } + } + }) + + t.Run("team admin", func(t *testing.T) { + rteams, resp := th.Client.GetTeamsForUser(th.BasicUser.Id, "") + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + }) + + t.Run("system admin", func(t *testing.T) { + rteams, resp := th.SystemAdminClient.GetTeamsForUser(th.BasicUser.Id, "") + CheckNoError(t, resp) + for _, rteam := range rteams { + if rteam.Id != team.Id && rteam.Id != team2.Id { + continue + } + + if rteam.Email == "" { + t.Fatal("should not have sanitized email") + } else if rteam.AllowedDomains == "" { + t.Fatal("should not have sanitized allowed domains") + } + } + }) +} + func TestGetTeamMember(t *testing.T) { th := Setup().InitBasic().InitSystemAdmin() defer th.TearDown() diff --git a/app/command.go b/app/command.go index 6e439537e..811294b6d 100644 --- a/app/command.go +++ b/app/command.go @@ -41,6 +41,11 @@ func (a *App) CreateCommandPost(post *model.Post, teamId string, response *model post.Message = parseSlackLinksToMarkdown(response.Text) post.CreateAt = model.GetMillis() + if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { + err := model.NewAppError("CreateCommandPost", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) + return nil, err + } + if response.Attachments != nil { parseSlackAttachment(post, response.Attachments) } diff --git a/app/command_test.go b/app/command_test.go index b37e78ea9..de4822436 100644 --- a/app/command_test.go +++ b/app/command_test.go @@ -45,3 +45,23 @@ func TestMoveCommand(t *testing.T) { assert.Nil(t, err) assert.EqualValues(t, targetTeam.Id, retrievedCommand.TeamId) } + +func TestCreateCommandPost(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + post := &model.Post{ + ChannelId: th.BasicChannel.Id, + UserId: th.BasicUser.Id, + Type: model.POST_SYSTEM_GENERIC, + } + + resp := &model.CommandResponse{ + Text: "some message", + } + + _, err := th.App.CreateCommandPost(post, th.BasicTeam.Id, resp) + if err == nil && err.Id != "api.context.invalid_param.app_error" { + t.Fatal("should have failed - bad post type") + } +} diff --git a/app/post.go b/app/post.go index d51ba7103..da5661ae2 100644 --- a/app/post.go +++ b/app/post.go @@ -29,6 +29,11 @@ func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError) channel = result.Data.(*model.Channel) } + if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { + err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) + return nil, err + } + if channel.DeleteAt != 0 { err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) return nil, err @@ -599,12 +604,14 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr // Get the posts postList := model.NewPostList() - if presult := <-a.Srv.Store.Post().GetPostsByIds(postIds); presult.Err != nil { - return nil, presult.Err - } else { - for _, p := range presult.Data.([]*model.Post) { - postList.AddPost(p) - postList.AddOrder(p.Id) + if len(postIds) > 0 { + if presult := <-a.Srv.Store.Post().GetPostsByIds(postIds); presult.Err != nil { + return nil, presult.Err + } else { + for _, p := range presult.Data.([]*model.Post) { + postList.AddPost(p) + postList.AddOrder(p.Id) + } } } diff --git a/app/team.go b/app/team.go index 4bfb617a8..1bc77c9f0 100644 --- a/app/team.go +++ b/app/team.go @@ -104,8 +104,6 @@ func (a *App) UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { return nil, result.Err } - oldTeam.Sanitize() - a.sendUpdatedTeamEvent(oldTeam) return oldTeam, nil @@ -124,16 +122,18 @@ func (a *App) PatchTeam(teamId string, patch *model.TeamPatch) (*model.Team, *mo return nil, err } - updatedTeam.Sanitize() - a.sendUpdatedTeamEvent(updatedTeam) return updatedTeam, nil } func (a *App) sendUpdatedTeamEvent(team *model.Team) { + sanitizedTeam := &model.Team{} + *sanitizedTeam = *team + sanitizedTeam.Sanitize() + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_UPDATE_TEAM, "", "", "", nil) - message.Add("team", team.ToJson()) + message.Add("team", sanitizedTeam.ToJson()) a.Go(func() { a.Publish(message) }) @@ -833,3 +833,19 @@ func (a *App) GetTeamIdFromQuery(query url.Values) (string, *model.AppError) { return "", nil } + +func SanitizeTeam(session model.Session, team *model.Team) *model.Team { + if !SessionHasPermissionToTeam(session, team.Id, model.PERMISSION_MANAGE_TEAM) { + team.Sanitize() + } + + return team +} + +func SanitizeTeams(session model.Session, teams []*model.Team) []*model.Team { + for _, team := range teams { + SanitizeTeam(session, team) + } + + return teams +} diff --git a/app/team_test.go b/app/team_test.go index 7992dd0c3..61ae03f74 100644 --- a/app/team_test.go +++ b/app/team_test.go @@ -179,3 +179,217 @@ func TestPermanentDeleteTeam(t *testing.T) { t.Fatal(err) } } + +func TestSanitizeTeam(t *testing.T) { + th := Setup() + defer th.TearDown() + + team := &model.Team{ + Id: model.NewId(), + Email: th.MakeEmail(), + AllowedDomains: "example.com", + } + copyTeam := func() *model.Team { + copy := &model.Team{} + *copy = *team + return copy + } + + t.Run("not a user of the team", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: model.NewId(), + Roles: model.ROLE_TEAM_USER.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email != "" && sanitized.AllowedDomains != "" { + t.Fatal("should've sanitized team") + } + }) + + t.Run("user of the team", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: team.Id, + Roles: model.ROLE_TEAM_USER.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email != "" && sanitized.AllowedDomains != "" { + t.Fatal("should've sanitized team") + } + }) + + t.Run("team admin", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: team.Id, + Roles: model.ROLE_TEAM_USER.Id + " " + model.ROLE_TEAM_ADMIN.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email == "" && sanitized.AllowedDomains == "" { + t.Fatal("shouldn't have sanitized team") + } + }) + + t.Run("team admin of another team", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: model.NewId(), + Roles: model.ROLE_TEAM_USER.Id + " " + model.ROLE_TEAM_ADMIN.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email != "" && sanitized.AllowedDomains != "" { + t.Fatal("should've sanitized team") + } + }) + + t.Run("system admin, not a user of team", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id + " " + model.ROLE_SYSTEM_ADMIN.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: model.NewId(), + Roles: model.ROLE_TEAM_USER.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email == "" && sanitized.AllowedDomains == "" { + t.Fatal("shouldn't have sanitized team") + } + }) + + t.Run("system admin, user of team", func(t *testing.T) { + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id + " " + model.ROLE_SYSTEM_ADMIN.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: team.Id, + Roles: model.ROLE_TEAM_USER.Id, + }, + }, + } + + sanitized := SanitizeTeam(session, copyTeam()) + if sanitized.Email == "" && sanitized.AllowedDomains == "" { + t.Fatal("shouldn't have sanitized team") + } + }) +} + +func TestSanitizeTeams(t *testing.T) { + th := Setup() + defer th.TearDown() + + t.Run("not a system admin", func(t *testing.T) { + teams := []*model.Team{ + { + Id: model.NewId(), + Email: th.MakeEmail(), + AllowedDomains: "example.com", + }, + { + Id: model.NewId(), + Email: th.MakeEmail(), + AllowedDomains: "example.com", + }, + } + + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: teams[0].Id, + Roles: model.ROLE_TEAM_USER.Id, + }, + { + UserId: userId, + TeamId: teams[1].Id, + Roles: model.ROLE_TEAM_USER.Id + " " + model.ROLE_TEAM_ADMIN.Id, + }, + }, + } + + sanitized := SanitizeTeams(session, teams) + + if sanitized[0].Email != "" && sanitized[0].AllowedDomains != "" { + t.Fatal("should've sanitized first team") + } + + if sanitized[1].Email == "" && sanitized[1].AllowedDomains == "" { + t.Fatal("shouldn't have sanitized second team") + } + }) + + t.Run("system admin", func(t *testing.T) { + teams := []*model.Team{ + { + Id: model.NewId(), + Email: th.MakeEmail(), + AllowedDomains: "example.com", + }, + { + Id: model.NewId(), + Email: th.MakeEmail(), + AllowedDomains: "example.com", + }, + } + + userId := model.NewId() + session := model.Session{ + Roles: model.ROLE_SYSTEM_USER.Id + " " + model.ROLE_SYSTEM_ADMIN.Id, + TeamMembers: []*model.TeamMember{ + { + UserId: userId, + TeamId: teams[0].Id, + Roles: model.ROLE_TEAM_USER.Id, + }, + }, + } + + sanitized := SanitizeTeams(session, teams) + + if sanitized[0].Email == "" && sanitized[0].AllowedDomains == "" { + t.Fatal("shouldn't have sanitized first team") + } + + if sanitized[1].Email == "" && sanitized[1].AllowedDomains == "" { + t.Fatal("shouldn't have sanitized second team") + } + }) +} diff --git a/app/webhook.go b/app/webhook.go index dbe444a25..231fe1529 100644 --- a/app/webhook.go +++ b/app/webhook.go @@ -131,6 +131,11 @@ func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, ove post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType} post.AddProp("from_webhook", "true") + if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { + err := model.NewAppError("CreateWebhookPost", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) + return nil, err + } + if metrics := a.Metrics; metrics != nil { metrics.IncrementWebhookPost() } diff --git a/app/webhook_test.go b/app/webhook_test.go index 5699addbf..b9ba35f43 100644 --- a/app/webhook_test.go +++ b/app/webhook_test.go @@ -44,4 +44,9 @@ func TestCreateWebhookPost(t *testing.T) { t.Fatal(k) } } + + _, 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") + } } diff --git a/i18n/de.json b/i18n/de.json index c50ae2982..760986963 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -2425,7 +2425,7 @@ }, { "id": "api.user.activate_mfa.email_and_ldap_only.app_error", - "translation": "MFA ist für diesen Accounttyp nicht verfügbar" + "translation": "MFA ist für diesen Kontotyp nicht verfügbar" }, { "id": "api.user.add_direct_channels_and_forget.failed.error", @@ -2465,7 +2465,7 @@ }, { "id": "api.user.check_user_login_attempts.too_many.app_error", - "translation": "Ihr Account ist gesperrt, da es zu viele fehlerhafte Passworteingaben gab. Bitte setzen Sie Ihr Passwort zurück." + "translation": "Ihr Konto ist gesperrt, da es zu viele fehlerhafte Passworteingaben gab. Bitte setzen Sie Ihr Passwort zurück." }, { "id": "api.user.check_user_mfa.bad_code.app_error", @@ -2493,11 +2493,11 @@ }, { "id": "api.user.create_oauth_user.already_attached.app_error", - "translation": "Mit dieser E-Mail-Adresse ist bereits ein Account verknüpft, der nicht die Anmeldemethode {{.Service}} verwendet. Bitte verwenden Sie {{.Auth}} zum Anmelden." + "translation": "Mit dieser E-Mail-Adresse ist bereits ein Konto verknüpft, das nicht die Anmeldemethode {{.Service}} verwendet. Bitte verwenden Sie {{.Auth}} zum Anmelden." }, { "id": "api.user.create_oauth_user.already_used.app_error", - "translation": "Dieser {{.Service}} Account wurde bereits zur Registrierung verwendet" + "translation": "Dieses {{.Service}} Konto wurde bereits zur Registrierung verwendet" }, { "id": "api.user.create_oauth_user.create.app_error", @@ -2597,7 +2597,7 @@ }, { "id": "api.user.ldap_to_email.not_ldap_account.app_error", - "translation": "Dieser Account verwendet kein AD/LDAP" + "translation": "Dieses Konto verwendet kein AD/LDAP" }, { "id": "api.user.login.blank_pwd.app_error", @@ -2605,7 +2605,7 @@ }, { "id": "api.user.login.inactive.app_error", - "translation": "Anmeldung nicht möglich, weil Ihr Account deaktiviert wurde. Bitte wenden Sie sich an einen Administrator." + "translation": "Anmeldung nicht möglich, weil Ihr Konto deaktiviert wurde. Bitte wenden Sie sich an einen Administrator." }, { "id": "api.user.login.invalid_credentials", @@ -2657,15 +2657,15 @@ }, { "id": "api.user.permanent_delete_user.attempting.warn", - "translation": "Versuche, Account %v id=%v permanent zu löschen" + "translation": "Versuche Konto %v id=%v permanent zu löschen" }, { "id": "api.user.permanent_delete_user.deleted.warn", - "translation": "Account permanent gelöscht %v id=%v" + "translation": "Konto permanent gelöscht %v id=%v" }, { "id": "api.user.permanent_delete_user.system_admin.warn", - "translation": "Sie möchten %v löschen, welcher ein Systemadministrator ist. Eventuell müssen Sie mithilfe der Kommandozeilentools einen anderen Account zum Systemadministrator machen." + "translation": "Sie möchten %v löschen, welcher ein Systemadministrator ist. Eventuell müssen Sie mithilfe der Kommandozeilentools ein anderes Konto zum Systemadministrator machen." }, { "id": "api.user.reset_password.invalid_link.app_error", @@ -2681,7 +2681,7 @@ }, { "id": "api.user.reset_password.sso.app_error", - "translation": "Kann Passwort für SSO-Accounts nicht zurücksetzen" + "translation": "Kann Passwort für SSO-Konten nicht zurücksetzen" }, { "id": "api.user.reset_password.wrong_team.app_error", @@ -2709,7 +2709,7 @@ }, { "id": "api.user.send_password_reset.find.app_error", - "translation": "Es konnte kein Account mit dieser Adresse gefunden werden." + "translation": "Es konnte kein Konto mit dieser Adresse gefunden werden." }, { "id": "api.user.send_password_reset.send.app_error", @@ -2717,7 +2717,7 @@ }, { "id": "api.user.send_password_reset.sso.app_error", - "translation": "Kann Passwort für SSO-Accounts nicht zurücksetzen" + "translation": "Kann Passwort für SSO-Konten nicht zurücksetzen" }, { "id": "api.user.send_sign_in_change_email_and_forget.error", @@ -2737,7 +2737,7 @@ }, { "id": "api.user.update_active.no_deactivate_ldap.app_error", - "translation": "Sie können den Aktivitätsstatus des AD-/LDAP-Accounts nicht ändern. Bitte ändern Sie diesen über den AD-/LDAP-Server." + "translation": "Sie können den Aktivitätsstatus des AD-/LDAP-Kontos nicht ändern. Bitte ändern Sie diesen über den AD-/LDAP-Server." }, { "id": "api.user.update_active.permissions.app_error", @@ -2761,7 +2761,7 @@ }, { "id": "api.user.update_password.incorrect.app_error", - "translation": "Das eingegebene \"Aktuelle Kennwort\" ist nicht korrekt. Bitte prüfen Sie, dass die Umschalt-Taste deaktiviert ist und versuchen es erneut." + "translation": "Das eingegebene \"Aktuelle Passwort\" ist nicht korrekt. Bitte prüfen Sie, dass die Umschalt-Taste deaktiviert ist und versuchen es erneut." }, { "id": "api.user.update_password.menu", @@ -2773,7 +2773,7 @@ }, { "id": "api.user.update_password.valid_account.app_error", - "translation": "Aktualisieren des Passworts fehlgeschlagen, da kein gültiger Account gefunden werden konnte" + "translation": "Aktualisieren des Passworts fehlgeschlagen, da kein gültiges Konto gefunden werden konnte" }, { "id": "api.user.update_roles.one_admin.app_error", @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "Lizenz unterstützt keine Datenaufbewahrung." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -3797,7 +3797,7 @@ }, { "id": "ent.ldap.create_fail", - "translation": "Konnte LDAP Benutzer nicht erstellen." + "translation": "Konnte LDAP-Benutzer nicht erstellen." }, { "id": "ent.ldap.disabled.app_error", @@ -3813,7 +3813,7 @@ }, { "id": "ent.ldap.do_login.licence_disable.app_error", - "translation": "AD/LDAP Funktionalität durch die aktuelle Lizenz deaktiviert. Bitte kontaktieren Sie Ihren Systemadministrator wegen eines Enterprise Lizenzupgrades." + "translation": "AD/LDAP-Funktionalität durch die aktuelle Lizenz deaktiviert. Bitte kontaktieren Sie ihren Systemadministrator wegen eines Upgrades ihrer Enterprise-Lizenz." }, { "id": "ent.ldap.do_login.matched_to_many_users.app_error", @@ -3833,7 +3833,7 @@ }, { "id": "ent.ldap.do_login.user_filtered.app_error", - "translation": "Ihr AD/LDAP Account hat keine Berechtigung diesen Mattermost Server zu benutzen. Bitte fragen Sie Ihren Systemadministrator, den AD/LDAP Benutzer Filter zu überprüfen." + "translation": "Ihr AD/LDAP-Konto hat keine Berechtigung diesen Mattermost-Server zu benutzen. Bitten Sie Ihren Systemadministrator den AD/LDAP-Benutzerfilter zu überprüfen." }, { "id": "ent.ldap.do_login.user_not_registered.app_error", @@ -3861,7 +3861,7 @@ }, { "id": "ent.ldap.validate_filter.app_error", - "translation": "Ungültiger AD/LDAP Filter" + "translation": "Ungültiger AD/LDAP-Filter" }, { "id": "ent.metrics.starting.info", @@ -4469,31 +4469,31 @@ }, { "id": "model.config.is_valid.ldap_basedn", - "translation": "AD/LDAP Feld \"BaseDN\" ist erforderlich." + "translation": "AD/LDAP-Feld \"BaseDN\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_bind_password", - "translation": "AD/LDAP Feld \"Bind Passwort\" ist erforderlich." + "translation": "AD/LDAP-Feld \"Bind Passwort\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_bind_username", - "translation": "AD/LDAP Feld \"Bind Benutzername\" ist erforderlich." + "translation": "AD/LDAP-Feld \"Bind Benutzername\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_email", - "translation": "AD/LDAP Feld \"E-Mail Attribut\" ist erforderlich." + "translation": "AD/LDAP-Feld \"E-Mail Attribut\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_firstname", - "translation": "AD/LDAP Feld \"Vornamenattribut\" ist erforderlich." + "translation": "AD/LDAP-Feld \"Vornamenattribut\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_id", - "translation": "AD/LDAP Feld \"ID Attribut\" ist erforderlich." + "translation": "AD/LDAP-Feld \"ID Attribut\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_lastname", - "translation": "AD/LDAP Feld \"Nachnameattribut\" ist erforderlich." + "translation": "AD/LDAP-Feld \"Nachnameattribut\" ist erforderlich." }, { "id": "model.config.is_valid.ldap_max_page_size.app_error", @@ -4501,15 +4501,15 @@ }, { "id": "model.config.is_valid.ldap_required.app_error", - "translation": "Notwendiges AD/LDAP Feld fehlt." + "translation": "Notwendiges AD/LDAP-Feld fehlt." }, { "id": "model.config.is_valid.ldap_required.app_error", - "translation": "Notwendiges AD/LDAP Feld fehlt." + "translation": "Notwendiges AD/LDAP-Feld fehlt." }, { "id": "model.config.is_valid.ldap_security.app_error", - "translation": "Ungültige Verbindungssicherheit in AD/LDAP Einstellungen. Muss '', 'TLS' oder 'STARTTLS' sein" + "translation": "Ungültige Verbindungssicherheit in AD/LDAP-Einstellungen. Muss '', 'TLS' oder 'STARTTLS' sein" }, { "id": "model.config.is_valid.ldap_server", @@ -4521,13 +4521,17 @@ }, { "id": "model.config.is_valid.ldap_username", - "translation": "AD/LDAP Feld \"Benutzername Attribut\" ist erforderlich." + "translation": "AD/LDAP-Feld \"Benutzername Attribut\" ist erforderlich." }, { "id": "model.config.is_valid.listen_address.app_error", "translation": "Ungültige Abhöradresse in Service Einstellungen. Muss gesetzt sein." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Verfügbare Sprachen muss Standardsprache des Clients enthalten" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Ungültige maximale Anzahl an Anmeldeversuchen in Service Einstellungen. Muss eine positive Zahl sein." }, @@ -5548,6 +5552,10 @@ "translation": "Die Anzahl der Befehle konnte nicht gezählt werden" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Der Befehl konnte nicht abgerufen werden" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Der Befehl konnte nicht gelöscht werden" }, @@ -6261,23 +6269,23 @@ }, { "id": "store.sql_user.get.app_error", - "translation": "Wir haben ein Problem beim Finden des Accounts" + "translation": "Wir haben ein Problem beim Finden des Kontos" }, { "id": "store.sql_user.get_all_using_auth_service.other.app_error", - "translation": "Es trat ein Fehler beim Suchen nach allen Accounts mit spezifischem Authentifizierungstyp auf." + "translation": "Es trat ein Fehler beim Suchen nach allen Konten mit einem spezifischem Authentifizierungstyp auf." }, { "id": "store.sql_user.get_by_auth.missing_account.app_error", - "translation": "Es konnte kein existierender Account entsprechend Ihres Authentifizierungstyps für dieses Team gefunden werden. Dieses Team erfordert eventuell eine Einladung vom Teambesitzer um beizutreten." + "translation": "Es konnte kein existierende Konto entsprechend Ihres Authentifizierungstyps für dieses Team gefunden werden. Dieses Team erfordert eventuell eine Einladung vom Teambesitzer, um beizutreten." }, { "id": "store.sql_user.get_by_auth.other.app_error", - "translation": "Es trat ein Fehler bei der Suche nach dem Account nach Authentifizierungstyp auf." + "translation": "Es trat ein Fehler bei der Suche nach dem Konto nach Authentifizierungstyp auf." }, { "id": "store.sql_user.get_by_username.app_error", - "translation": "Es konnte kein existierender Account entsprechend Ihres Benutzernamens für dieses Teams gefunden werden. Dieses Team erfordert eventuell eine Einladung vom Teambesitzer um beizutreten." + "translation": "Es konnte kein existierendes Konto entsprechend Ihres Benutzernamens für dieses Teams gefunden werden. Dieses Team erfordert eventuell eine Einladung vom Teambesitzer um beizutreten." }, { "id": "store.sql_user.get_for_login.app_error", @@ -6329,19 +6337,19 @@ }, { "id": "store.sql_user.save.app_error", - "translation": "Der Account konnte nicht gespeichert werden." + "translation": "Das Konto konnte nicht gespeichert werden." }, { "id": "store.sql_user.save.email_exists.app_error", - "translation": "Es gibt bereits einen Account mit dieser E-Mail-Adresse." + "translation": "Es gibt bereits ein Konto mit dieser E-Mail-Adresse." }, { "id": "store.sql_user.save.email_exists.ldap_app_error", - "translation": "Dieser Account verwendet keine AD/LDAP-Authentifizierung. Bitte melden Sie sich mit E-Mail-Adresse und Passwort an." + "translation": "Dieses Konto verwendet keine AD/LDAP-Authentifizierung. Bitte melden Sie sich mit E-Mail-Adresse und Passwort an." }, { "id": "store.sql_user.save.email_exists.saml_app_error", - "translation": "Dieser Account verwendet keine SAML-Authentifizierung. Bitte melden Sie sich mit E-Mail-Adresse und Passwort an." + "translation": "Dieses Konto verwendet keine SAML-Authentifizierung. Bitte melden Sie sich mit E-Mail-Adresse und Passwort an." }, { "id": "store.sql_user.save.existing.app_error", @@ -6349,7 +6357,7 @@ }, { "id": "store.sql_user.save.max_accounts.app_error", - "translation": "Dieses Team hat die maximale Anzahl an erlaubten Accounts erreicht. Kontaktieren Sie Ihren Systemadministrator um ein höheres Limit setzen zu lassen." + "translation": "Dieses Team hat die maximale Anzahl erlaubter Konten erreicht. Kontaktieren Sie Ihren Systemadministrator, um eine höhere Begrenzung setzen zu lassen." }, { "id": "store.sql_user.save.member_count.app_error", @@ -6357,15 +6365,15 @@ }, { "id": "store.sql_user.save.username_exists.app_error", - "translation": "Es gibt bereits einen Account mit diesem Benutzernamen." + "translation": "Es gibt bereits ein Konto mit diesem Benutzernamen." }, { "id": "store.sql_user.save.username_exists.ldap_app_error", - "translation": "Ein Account mit diesem Benutzernamen existiert bereits. Bitte kontaktieren Sie Ihren Administrator." + "translation": "Ein Konto mit diesem Benutzernamen existiert bereits. Bitte kontaktieren Sie Ihren Administrator." }, { "id": "store.sql_user.save.username_exists.saml_app_error", - "translation": "Ein Account mit diesem Benutzernamen existiert bereits. Bitte kontaktieren Sie Ihren Administrator." + "translation": "Ein Konto mit diesem Benutzernamen existiert bereits. Bitte kontaktieren Sie Ihren Administrator." }, { "id": "store.sql_user.update.app_error", @@ -6401,7 +6409,7 @@ }, { "id": "store.sql_user.update_auth_data.email_exists.app_error", - "translation": "Es war nicht möglich den Account zu {{.Service}} zu wechseln. Es existiert bereits ein Account mit der E-Mail-Adresse {{.Email}}." + "translation": "Es war nicht möglich, das Konto zu {{.Service}} zu wechseln. Es existiert bereits ein Konto mit der E-Mail-Adresse {{.Email}}." }, { "id": "store.sql_user.update_failed_pwd_attempts.app_error", @@ -6721,7 +6729,7 @@ }, { "id": "web.claim_account.title", - "translation": "Account beanspruchen" + "translation": "Konto beanspruchen" }, { "id": "web.claim_account.user.error", diff --git a/i18n/en.json b/i18n/en.json index df30fd9d5..4c09b8eba 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -4384,10 +4384,6 @@ "translation": "Message retention must be one day or longer." }, { - "id": "model.config.is_valid.localization.available_locales.app_error", - "translation": "Available Languages must contain Default Client Language" - }, - { "id": "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", "translation": "Elasticsearch AggregatePostsAfterDays setting must be a number greater than or equal to 1" }, @@ -4532,6 +4528,10 @@ "translation": "Invalid listen address for service settings Must be set." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Invalid maximum login attempts for service settings. Must be a positive number." }, @@ -5552,6 +5552,10 @@ "translation": "We couldn't count the commands" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "We couldn't get the command" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "We couldn't delete the command" }, @@ -5580,10 +5584,6 @@ "translation": "We couldn't update the command" }, { - "id": "store.sql_command.get_by_trigger.app_error", - "translation": "We couldn't get the command" - }, - { "id": "store.sql_command_webhooks.get.app_error", "translation": "We couldn't get the webhook" }, diff --git a/i18n/es.json b/i18n/es.json index ba0e8fcbe..9b1a82b32 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "La licencia no admite la Retención de Datos." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -4528,6 +4528,10 @@ "translation": "Dirección dónde se escuchará el servicio en la configuracón del servicio debe ser asignada." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Los Idiomas disponibles debe contener el idioma del cliente predeterminado" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Número inválido de máximos intentos de inició de sesión en la configuración del servicio. Debe ser un número positivo." }, @@ -5548,6 +5552,10 @@ "translation": "No pudimos contar los comandos de barra" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "No pudimos obtener el comando" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "No pudimos eliminar el comando" }, diff --git a/i18n/fr.json b/i18n/fr.json index d8b97ac5d..890e16ac2 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -4528,6 +4528,10 @@ "translation": "Adresse d'écoute invalide dans les paramètres de service. Doit être renseignée." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Nombre maximum de tentatives de connexion invalide dans les paramètre de service. Doit être un entier positif." }, @@ -5548,6 +5552,10 @@ "translation": "Impossible de compter les commandes" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Impossible de récupérer la commande" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Impossible de supprimer la commande" }, diff --git a/i18n/it.json b/i18n/it.json index 8edcbbd47..04657ff6d 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "La licenza non supporta la ritenzione dei dati." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -4528,6 +4528,10 @@ "translation": "Indirizzo di ascolto per i servizi non valido Deve essere impostato." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Le Lingue Disponibile devono contenere la Lingua di Default per il Client" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Numero massimo tentativi di login non valido. Deve essere un numero positivo." }, @@ -5548,6 +5552,10 @@ "translation": "Impossibile contare comandi" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Non è possibile trovare il comando" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Impossibile cancellare comando" }, diff --git a/i18n/ja.json b/i18n/ja.json index db0d221a8..8cffb4ec3 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "ライセンスはデータ保持をサポートしていません。" }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -4528,6 +4528,10 @@ "translation": "サービス設定の接続待ちアドレスが不正です。設定してください。" }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "利用可能な言語はデフォルトのクライアント言語を含んでいなくてはなりません" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "サービス設定の最大ログイン試行回数が不正です。正の数を指定してください。" }, @@ -5548,6 +5552,10 @@ "translation": "コマンド数を数えられませんでした" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "コマンドが取得できませんでした" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "コマンドを削除できませんでした" }, diff --git a/i18n/ko.json b/i18n/ko.json index f39210511..7baf1545d 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -4528,6 +4528,10 @@ "translation": "Invalid listen address for service settings Must be set." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Invalid maximum login attempts for service settings. Must be a positive number." }, @@ -5548,6 +5552,10 @@ "translation": "채널을 찾을 수 없습니다" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "채널을 찾을 수 없습니다" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "채널을 찾을 수 없습니다" }, diff --git a/i18n/nl.json b/i18n/nl.json index 25720589b..223a2731f 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -4528,6 +4528,10 @@ "translation": "Ongeldig luister adres bij de service instellingen. Deze moet geconfigureerd zijn." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Maximaal aantal inlog poging bij service instellingen. Moet een getal grote dan 0 zijn." }, @@ -5548,6 +5552,10 @@ "translation": "We kunnen de opdrachten niet tellen" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "We kunnen de opdracht niet ophalen" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "De opdracht kan niet verwijderd worden" }, diff --git a/i18n/pl.json b/i18n/pl.json index 8588e9cad..ceecaeb76 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -1,7 +1,7 @@ [ { "id": "April", - "translation": "kwiecień" + "translation": "Kwiecień" }, { "id": "August", @@ -185,11 +185,11 @@ }, { "id": "api.channel.can_manage_channel.private_restricted_system_admin.app_error", - "translation": "Zarządzanie i tworzenie kanałów prywatnych jest dostępne tylko dla Administratorów Systemu." + "translation": "Zarządzanie i tworzenie kanałów prywatnych jest dostępne tylko dla administratorów systemu." }, { "id": "api.channel.can_manage_channel.private_restricted_team_admin.app_error", - "translation": "Zarządzanie i tworzenie kanałów prywatnych jest dostępne tylko dla Zespołów i Administratorów Systemu." + "translation": "Zarządzanie i tworzenie kanałów prywatnych jest dostępne tylko dla administratorów systemu i zespołów." }, { "id": "api.channel.can_manage_channel.public_restricted_system_admin.app_error", @@ -861,7 +861,7 @@ }, { "id": "api.command_shortcuts.unsupported.app_error", - "translation": "Polecenie wyszukiwania nie jest wspierana na Twoim urządzeniu" + "translation": "Polecenie skrótu nie jest wspierane na Twoim urządzeniu" }, { "id": "api.command_shrug.desc", @@ -1103,7 +1103,7 @@ }, { "id": "api.file.get_file.public_disabled.app_error", - "translation": "Linki publiczne zapisu została wyłączona przez administratora systemu." + "translation": "Linki publiczne zostały wyłączone przez administratora systemu." }, { "id": "api.file.get_file.public_invalid.app_error", @@ -1587,7 +1587,7 @@ }, { "id": "api.post.do_action.action_id.app_error", - "translation": "Nieprawidłowy identyfikator klienta" + "translation": "Nieprawidłowe id akcji" }, { "id": "api.post.do_action.action_integration.app_error", @@ -1697,7 +1697,7 @@ }, { "id": "api.post.send_notifications_and_forget.push_mention_no_channel", - "translation": " wspomniał o Tobie w " + "translation": "wspomniał cię" }, { "id": "api.post.send_notifications_and_forget.push_message", @@ -1857,7 +1857,7 @@ }, { "id": "api.slackimport.slack_add_channels.added", - "translation": "\r\n kanały dodane \r\n" + "translation": "\r\nDodane kanały:\r\n" }, { "id": "api.slackimport.slack_add_channels.failed_to_add_user", @@ -1929,7 +1929,7 @@ }, { "id": "api.slackimport.slack_add_users.created", - "translation": "\r\n użytkowników zostało utworzonych\r\n" + "translation": "\r\nUtworzeni użytkownicy:\r\n" }, { "id": "api.slackimport.slack_add_users.email_pwd", @@ -1945,7 +1945,7 @@ }, { "id": "api.slackimport.slack_add_users.missing_email_address", - "translation": "Użytkownik {{.Username}} nie posiada adresu email w Slack export. Użyto {{.Email}} jako zastępnik. Użytkownik powinien zaktualizować adres email po zalogowaniu do systemu.\r\n" + "translation": "Użytkownik {{.Username}} nie ma adresu e-mail w eksporcie Slacka. Użyto {{.Email}} jako placeholdera. Użytkownik powinien zaktualizować adres po zalogowaniu.\r\n" }, { "id": "api.slackimport.slack_add_users.missing_email_address.warn", @@ -1953,7 +1953,7 @@ }, { "id": "api.slackimport.slack_add_users.unable_import", - "translation": "Nie można zaimportować użytkownika: {{.Username}}\r\n" + "translation": "Nie można zaimportować użytkownika: {{.Username}}.\r\n" }, { "id": "api.slackimport.slack_convert_channel_mentions.compile_regexp_failed.warn", @@ -1965,11 +1965,11 @@ }, { "id": "api.slackimport.slack_convert_user_mentions.compile_regexp_failed.warn", - "translation": "Slack Import: Nie można sakompilować !channel, pasującego do regular expressions dla kanału Slacka {{.ChannelName}} (id={{.ChannelID}})." + "translation": "Slack Import: Nie można skompilować @mention, pasującej do regular expressions dla kanału Slacka {{.ChannelName}} (id={{.ChannelID}})." }, { "id": "api.slackimport.slack_deactivate_bot_user.failed_to_deactivate", - "translation": "Slack Import: Unable to deactivate the user account used for the bot." + "translation": "Slack Import: Nie można zdezaktywować konta użytkownika używanego przez bota." }, { "id": "api.slackimport.slack_import.log", @@ -1993,7 +1993,7 @@ }, { "id": "api.slackimport.slack_import.open.app_error", - "translation": "Unable to open the file: {{.Filename}}.\r\n" + "translation": "Nie można otworzyć pliku: {{.Filename}}.\r\n" }, { "id": "api.slackimport.slack_import.team_fail", @@ -2001,15 +2001,15 @@ }, { "id": "api.slackimport.slack_import.zip.app_error", - "translation": "Unable to open the Slack export zip file.\r\n" + "translation": "Nie można otworzyć pliku zip z eksportem Slacka.\r\n" }, { "id": "api.slackimport.slack_parse_channels.error", - "translation": "Slack Import: Error occurred when parsing some Slack channels. Import may work anyway." + "translation": "Slack Import: Wystąpił błąd parsowania kanałów Slacka. Import może działać pomimo tego." }, { "id": "api.slackimport.slack_parse_posts.error", - "translation": "Slack Import: Error occurred when parsing some Slack posts. Import may work anyway." + "translation": "Slack Import: Wystąpił błąd parsowania postów Slacka. Import może działać pomimo tego." }, { "id": "api.slackimport.slack_sanitise_channel_properties.display_name_too_long.warn", @@ -2125,7 +2125,7 @@ }, { "id": "api.team.invite_members.invalid_email.app_error", - "translation": "The following email addresses do not belong to an accepted domain: {{.Addresses}}. Please contact your System Administrator for details." + "translation": "Następujące adresy e-mail nie należą do akceptowanej domeny: {{.Addresses}}. Skontaktuj się z administratorem systemu, by dowiedzieć się więcej." }, { "id": "api.team.invite_members.member", @@ -2141,7 +2141,7 @@ }, { "id": "api.team.invite_members.restricted_team_admin.app_error", - "translation": "Zapraszanie nowych użytkowników do zespołu ogranicza się do zespołu i administratorów systemu." + "translation": "Zapraszanie nowych użytkowników do zespołu ogranicza się do administratorów systemu i zespołu." }, { "id": "api.team.invite_members.send.error", @@ -2157,7 +2157,7 @@ }, { "id": "api.team.is_team_creation_allowed.domain.app_error", - "translation": "List musi być z określonej domeny (np. @example.com). Proszę, skontaktuj się z administratorem systemu, aby poznać szczegóły." + "translation": "E-mail musi być z określonej domeny (np. @example.com). Proszę, skontaktuj się z administratorem systemu, aby poznać szczegóły." }, { "id": "api.team.permanent_delete_team.attempting.warn", @@ -2189,7 +2189,7 @@ }, { "id": "api.templates.email_change_body.info", - "translation": "Twój adres e-mail {{.TeamDisplayName}} został zmieniony na {{.NewEmail}}.<br>Jeśli nie to proszę, skontaktuj się z administratorem systemu." + "translation": "Twój adres e-mail na {{.TeamDisplayName}} został zmieniony na {{.NewEmail}}.<br>Jeśli to nie ty dokonałeś tej zmiany, skontaktuj się z administratorem systemu." }, { "id": "api.templates.email_change_body.title", @@ -2357,15 +2357,15 @@ }, { "id": "api.templates.user_access_token_body.info", - "translation": "A personal access token was added to your account on {{ .SiteURL }}. They can be used to access {{.SiteName}} with your account.<br>If this change wasn't initiated by you, please contact your system administrator." + "translation": "Osobisty token dostępu został dodany do twojego konta na {{ .SiteURL }}. Można ich użyć by logować się na {{.SiteName}} przy użyciu twojego konta.<br>Jeżeli ta zmiana nie została dokonana przez ciebie, skontaktuj się z administratorem systemu." }, { "id": "api.templates.user_access_token_body.title", - "translation": "Personal access token added to your account" + "translation": "Osobisty token dostępu został dodany do twojego konta" }, { "id": "api.templates.user_access_token_subject", - "translation": "[{{ .SiteName }}] Personal access token added to your account" + "translation": "[{{ .SiteName }}] Osobisty token dostępu został dodany do twojego konta" }, { "id": "api.templates.username_change_body.info", @@ -2981,7 +2981,7 @@ }, { "id": "app.channel.move_channel.members_do_not_match.error", - "translation": "Cannot move a channel unless all its members are already members of the destination team." + "translation": "Nie można przenieść kanału, dopóki wszyscy jego członkowie nie są członkami zespołu docelowego." }, { "id": "app.channel.post_update_channel_purpose_message.post.error", @@ -3045,7 +3045,7 @@ }, { "id": "app.import.import_direct_post.save_preferences.error", - "translation": "Error importing direct post. Failed to save preferences." + "translation": "Wystąpił błąd podczas importowania wiadomości bezpośrednich. Nie udało się zapisać preferencji." }, { "id": "app.import.import_direct_post.user_not_found.error", @@ -3085,7 +3085,7 @@ }, { "id": "app.import.import_post.save_preferences.error", - "translation": "Error importing post. Failed to save preferences." + "translation": "Wystąpił błąd podczas importowania wiadomości. Nie udało się zapisać preferencji." }, { "id": "app.import.import_post.team_not_found.error", @@ -3097,7 +3097,7 @@ }, { "id": "app.import.import_user_channels.save_preferences.error", - "translation": "Error importing user channel memberships. Failed to save preferences." + "translation": "Wystąpił błąd podczas importowania przynależności użytkowników do kanałów. Nie udało się zapisać preferencji." }, { "id": "app.import.validate_channel_import_data.create_at_zero.error", @@ -3161,7 +3161,7 @@ }, { "id": "app.import.validate_direct_channel_import_data.unknown_favoriter.error", - "translation": "Direct channel can only be favorited by members. \"{{.Username}}\" is not a member." + "translation": "Kanał bezpośredni może być polubiony tylko przez członków. \"{{.Username}}\" nie jest członkiem." }, { "id": "app.import.validate_direct_post_import_data.channel_members_required.error", @@ -3181,7 +3181,7 @@ }, { "id": "app.import.validate_direct_post_import_data.create_at_zero.error", - "translation": "CreateAt must be greater than 0" + "translation": "CreateAt musi być większe od 0" }, { "id": "app.import.validate_direct_post_import_data.message_length.error", @@ -3193,7 +3193,7 @@ }, { "id": "app.import.validate_direct_post_import_data.unknown_flagger.error", - "translation": "Direct post can only be flagged by members of the channel it is in. \"{{.Username}}\" is not a member." + "translation": "Bezpośrednia wiadomość może być oznaczona tylko przez członków kanału, w którym się znajduje. \"{{.Username}}\" nie jest członkiem." }, { "id": "app.import.validate_direct_post_import_data.user_missing.error", @@ -3325,11 +3325,11 @@ }, { "id": "app.import.validate_user_import_data.notify_props_channel_trigger_invalid.error", - "translation": "Invalid Channel Trigger Notify Prop for user." + "translation": "Nieprawidłowy ciąg znaków powiadamiania o kanale dla użytkownika." }, { "id": "app.import.validate_user_import_data.notify_props_comment_trigger_invalid.error", - "translation": "Invalid Comment Trigger Notify Prop for user." + "translation": "Nieprawidłowy ciąg znaków powiadamiania o komentarzu dla użytkownika." }, { "id": "app.import.validate_user_import_data.notify_props_desktop_duration_invalid.error", @@ -4528,6 +4528,10 @@ "translation": "Nieprawidłowy adres nasłuchiwania dla ustawień usługi." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Nieprawidłowa maksymalna ilość prób logowania dla ustawień usługi. Musi być liczbą większą od 0." }, @@ -5548,6 +5552,10 @@ "translation": "Nie możemy policzyć poleceń" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Nie można pobrać polecenia" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Nie można usunąć polecenia" }, diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 29b500ad8..d4b66e90c 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "Licença não suporta Retenção de Dados." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -4528,6 +4528,10 @@ "translation": "Inválido endereço de escuta em configurações de serviço Deve ser ajustado." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Idiomas Disponíveis devem conter Idioma Padrão do Cliente" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Inválido máxima tentativas de login em configurações de serviço. Deve ser um número positivo." }, @@ -5548,6 +5552,10 @@ "translation": "Não foi possível contar os comandos" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Não foi possível obter o comando" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Não foi possível deletar o comando" }, diff --git a/i18n/ru.json b/i18n/ru.json index 72e8f2fa4..8adb44b16 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -4528,6 +4528,10 @@ "translation": "Неверный адрес прослушивания в настройках службы. Должен быть задан." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Available Languages must contain Default Client Language" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "Неверное максимальное количество попыток входа в настройках службы. Должно быть положительным числом." }, @@ -5548,6 +5552,10 @@ "translation": "Не удалось подсчитать команды" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Не удалось получить команду" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Безуспешная попытка удалить команду" }, diff --git a/i18n/tr.json b/i18n/tr.json index e332f9e43..77a9cb689 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "Lisans Veri Yedeklemeyi kapsamıyor." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -4528,6 +4528,10 @@ "translation": "Dinleme adresi hizmet ayarı geçersiz. Ayarlanmalı." }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "Varsayılan İstemci Dili kullanılabilecek diller arasında bulunmalıdır." + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "En fazla oturum açma girişimi hizmet ayarı geçersiz. Pozitif bir sayı olmalıdır." }, @@ -5548,6 +5552,10 @@ "translation": "Komutlar sayılamadı" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "Komut alınamadı" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "Komut silinemedi" }, diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 33556f6a2..820e0a1fb 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -3597,7 +3597,7 @@ }, { "id": "cli.license.critical", - "translation": "Feature requires an upgrade to Enterprise Edition and the inclusion of a license key. Please contact your System Administrator." + "translation": "功能需要升级到企业版本并拥有许可证。请联系您的系统管理员。" }, { "id": "ent.brand.save_brand_image.decode.app_error", @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "许可证不支持数据保留。" }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -3845,11 +3845,11 @@ }, { "id": "ent.ldap.sync.index_job_failed.error", - "translation": "LDAP sync worker failed due to the sync job failing" + "translation": "LDAP 同步工作者失败因为同步任务失败" }, { "id": "ent.ldap.sync_worker.create_index_job.error", - "translation": "LDAP sync worker failed to create the sync job" + "translation": "LDAP 同步工作者创建同步任务失败" }, { "id": "ent.ldap.syncdone.info", @@ -4373,15 +4373,15 @@ }, { "id": "model.config.is_valid.data_retention.deletion_job_start_time.app_error", - "translation": "Data retention job start time must be a 24-hour time stamp in the form HH:MM." + "translation": "数据保留任务开始时间必须为 24 小时制并格式为 HH:MM。" }, { "id": "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", - "translation": "File retention must be one day or longer." + "translation": "文件保留至少一天。" }, { "id": "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", - "translation": "Message retention must be one day or longer." + "translation": "消息保留至少一天。" }, { "id": "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", @@ -4528,6 +4528,10 @@ "translation": "无效的服务设置时监听地址,必须设置此项。" }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "可选语言必须包含默认客户端语言" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "无效的最大尝试登录数服务设置。必须是正整数。" }, @@ -5289,15 +5293,15 @@ }, { "id": "store.sql_audit.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of audits" + "translation": "批量永久删除审计时遇到错误" }, { "id": "store.sql_audit.permanent_delete_by_user.app_error", - "translation": "我们删除审核时遇到了一个错误" + "translation": "我们删除审计时遇到了一个错误" }, { "id": "store.sql_audit.save.saving.app_error", - "translation": "我们保持审核时出错" + "translation": "我们保存审计时遇到错误" }, { "id": "store.sql_channel.analytics_deleted_type_count.app_error", @@ -5548,6 +5552,10 @@ "translation": "我们无法计算指令数量" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "我们无法获取这个命令" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "我们无法删除命令" }, @@ -5653,7 +5661,7 @@ }, { "id": "store.sql_file_info.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of file infos" + "translation": "批量永久删除文件信息时遇到错误" }, { "id": "store.sql_file_info.save.app_error", @@ -5677,11 +5685,11 @@ }, { "id": "store.sql_job.get_count_by_status_and_type.app_erro", - "translation": "We couldn't get the job count by status and type" + "translation": "我们无法以状态和类型获取任务数" }, { "id": "store.sql_job.get_newest_job_by_status_and_type.app_error", - "translation": "We couldn't get the newest job by status and type" + "translation": "我们无法以状态和类型获取最新任务" }, { "id": "store.sql_job.save.app_error", @@ -5873,11 +5881,11 @@ }, { "id": "store.sql_post.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of posts" + "translation": "批量永久删除消息时遇到错误" }, { "id": "store.sql_post.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of posts" + "translation": "批量永久删除消息时遇到错误" }, { "id": "store.sql_post.permanent_delete_by_channel.app_error", @@ -5913,7 +5921,7 @@ }, { "id": "store.sql_preference.cleanup_flags_batch.app_error", - "translation": "We encountered an error cleaning up the batch of flags" + "translation": "清理批量标记时遇到错误" }, { "id": "store.sql_preference.delete.app_error", @@ -6005,7 +6013,7 @@ }, { "id": "store.sql_reaction.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of reactions" + "translation": "批量永久删除反应时遇到错误" }, { "id": "store.sql_reaction.save.begin.app_error", diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 90e244f41..012ffae2a 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -3441,7 +3441,7 @@ }, { "id": "app.plugin.filesystem.app_error", - "translation": "Encountered filesystem error" + "translation": "遇到檔案系統錯誤" }, { "id": "app.plugin.get_plugins.app_error", @@ -3597,7 +3597,7 @@ }, { "id": "cli.license.critical", - "translation": "Feature requires an upgrade to Enterprise Edition and the inclusion of a license key. Please contact your System Administrator." + "translation": "功能需要升級至企業版並加入授權碼。請聯絡系統管理員。" }, { "id": "ent.brand.save_brand_image.decode.app_error", @@ -3689,7 +3689,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "現有授權不支援資料保留。" }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -3729,7 +3729,7 @@ }, { "id": "ent.elasticsearch.data_retention_delete_indexes.get_indexes.error", - "translation": "建立 Elasticsearch 索引時失敗" + "translation": "取得 Elasticsearch 索引時失敗" }, { "id": "ent.elasticsearch.delete_post.error", @@ -3845,11 +3845,11 @@ }, { "id": "ent.ldap.sync.index_job_failed.error", - "translation": "LDAP sync worker failed due to the sync job failing" + "translation": "由於同步工作失敗,LDAP 同步工作失敗。" }, { "id": "ent.ldap.sync_worker.create_index_job.error", - "translation": "LDAP sync worker failed to create the sync job" + "translation": "LDAP 同步工作無法建立同步工作" }, { "id": "ent.ldap.syncdone.info", @@ -4373,15 +4373,15 @@ }, { "id": "model.config.is_valid.data_retention.deletion_job_start_time.app_error", - "translation": "Data retention job start time must be a 24-hour time stamp in the form HH:MM." + "translation": "資料保留工作起始時間必須為 HH:MM 格式的 24 小時時間戳記。" }, { "id": "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", - "translation": "File retention must be one day or longer." + "translation": "檔案保留需至少一天。" }, { "id": "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", - "translation": "Message retention must be one day or longer." + "translation": "訊息保留需至少一天。" }, { "id": "model.config.is_valid.elastic_search.aggregate_posts_after_days.app_error", @@ -4528,6 +4528,10 @@ "translation": "服務設定中的聆聽位址無效。此項目必須設定" }, { + "id": "model.config.is_valid.localization.available_locales.app_error", + "translation": "可用的語言必須包含用戶端預設語言" + }, + { "id": "model.config.is_valid.login_attempts.app_error", "translation": "服務設定中的最多登入嘗試次數無效。必須為正數." }, @@ -5289,7 +5293,7 @@ }, { "id": "store.sql_audit.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of audits" + "translation": "永久批次刪除稽核時遇到錯誤" }, { "id": "store.sql_audit.permanent_delete_by_user.app_error", @@ -5548,6 +5552,10 @@ "translation": "無法計算命令數量" }, { + "id": "store.sql_command.get_by_trigger.app_error", + "translation": "無法取得命令" + }, + { "id": "store.sql_command.save.delete.app_error", "translation": "無法刪除命令" }, @@ -5653,7 +5661,7 @@ }, { "id": "store.sql_file_info.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of file infos" + "translation": "永久批次刪除檔案資訊時遇到錯誤" }, { "id": "store.sql_file_info.save.app_error", @@ -5677,11 +5685,11 @@ }, { "id": "store.sql_job.get_count_by_status_and_type.app_erro", - "translation": "We couldn't get the job count by status and type" + "translation": "無法根據狀態與類型取得工作數量" }, { "id": "store.sql_job.get_newest_job_by_status_and_type.app_error", - "translation": "We couldn't get the newest job by status and type" + "translation": "無法根據狀態與類型取得最新工作" }, { "id": "store.sql_job.save.app_error", @@ -5873,11 +5881,11 @@ }, { "id": "store.sql_post.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of posts" + "translation": "永久批次刪除訊息時遇到錯誤" }, { "id": "store.sql_post.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of posts" + "translation": "永久批次刪除訊息時遇到錯誤" }, { "id": "store.sql_post.permanent_delete_by_channel.app_error", @@ -5913,7 +5921,7 @@ }, { "id": "store.sql_preference.cleanup_flags_batch.app_error", - "translation": "We encountered an error cleaning up the batch of flags" + "translation": "永久批次清除標記時遇到錯誤" }, { "id": "store.sql_preference.delete.app_error", @@ -6005,7 +6013,7 @@ }, { "id": "store.sql_reaction.permanent_delete_batch.app_error", - "translation": "We encountered an error permanently deleting the batch of reactions" + "translation": "永久批次刪除回應時遇到錯誤" }, { "id": "store.sql_reaction.save.begin.app_error", @@ -6585,15 +6593,15 @@ }, { "id": "utils.file.list_directory.configured.app_error", - "translation": "檔案儲存位置設定不正確。請設定為 S3 或是本地儲存。" + "translation": "檔案儲存位置設定不正確。請設定為 S3 或是本地伺服器檔案儲存空間。" }, { "id": "utils.file.list_directory.local.app_error", - "translation": "從本地儲存讀取時遇到錯誤" + "translation": "從本地伺服器儲存空間表列目錄時遇到錯誤" }, { "id": "utils.file.list_directory.s3.app_error", - "translation": "從 S3 刪除目錄時遇到錯誤" + "translation": "從 S3 表列目錄時遇到錯誤" }, { "id": "utils.file.remove_directory.configured.app_error", @@ -6781,11 +6789,11 @@ }, { "id": "web.error.unsupported_browser.message", - "translation": "Your current browser is not supported. Please upgrade to one of the following browsers:" + "translation": "當前的瀏覽器不被支援。請升級至下列瀏覽器之一:" }, { "id": "web.error.unsupported_browser.title", - "translation": "Unsupported Browser" + "translation": "不支援的瀏覽器" }, { "id": "web.find_team.title", diff --git a/model/client.go b/model/client.go index 54d8fc859..37f014e6b 100644 --- a/model/client.go +++ b/model/client.go @@ -429,7 +429,7 @@ func (c *Client) UpdateTeam(team *Team) (*Result, *AppError) { } else { defer closeBody(r) return &Result{r.Header.Get(HEADER_REQUEST_ID), - r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil + r.Header.Get(HEADER_ETAG_SERVER), TeamFromJson(r.Body)}, nil } } diff --git a/utils/config.go b/utils/config.go index c4ffbc8e0..2a956dd4a 100644 --- a/utils/config.go +++ b/utils/config.go @@ -518,9 +518,9 @@ func getClientConfig(c *model.Config) map[string]string { if *License.Features.LDAP { props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable) props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName - props["NicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "") - props["FirstNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.FirstNameAttribute != "") - props["LastNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.LastNameAttribute != "") + props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "") + props["LdapFirstNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.FirstNameAttribute != "") + props["LdapLastNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.LastNameAttribute != "") } if *License.Features.MFA { @@ -535,9 +535,9 @@ func getClientConfig(c *model.Config) map[string]string { if *License.Features.SAML { props["EnableSaml"] = strconv.FormatBool(*c.SamlSettings.Enable) props["SamlLoginButtonText"] = *c.SamlSettings.LoginButtonText - props["FirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "") - props["LastNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.LastNameAttribute != "") - props["NicknameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.NicknameAttribute != "") + props["SamlFirstNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.FirstNameAttribute != "") + props["SamlLastNameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.LastNameAttribute != "") + props["SamlNicknameAttributeSet"] = strconv.FormatBool(*c.SamlSettings.NicknameAttribute != "") } if *License.Features.Cluster { diff --git a/utils/mail.go b/utils/mail.go index 7be1303d1..9bda4ee39 100644 --- a/utils/mail.go +++ b/utils/mail.go @@ -7,14 +7,13 @@ import ( "crypto/tls" "mime" "net" + "net/http" "net/mail" "net/smtp" "time" "gopkg.in/gomail.v2" - "net/http" - l4g "github.com/alecthomas/log4go" "github.com/mattermost/html2text" "github.com/mattermost/mattermost-server/model" @@ -48,6 +47,20 @@ func connectToSMTPServer(config *model.Config) (net.Conn, *model.AppError) { return conn, nil } +// TODO: Remove once this bug is fixed: https://github.com/golang/go/issues/22166 +type plainAuthOverTLSConn struct { + smtp.Auth +} + +func PlainAuthOverTLSConn(identity, username, password, host string) smtp.Auth { + return &plainAuthOverTLSConn{smtp.PlainAuth(identity, username, password, host)} +} + +func (a *plainAuthOverTLSConn) Start(server *smtp.ServerInfo) (string, []byte, error) { + server.TLS = true + return a.Auth.Start(server) +} + func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.AppError) { c, err := smtp.NewClient(conn, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) if err != nil { @@ -73,7 +86,12 @@ func newSMTPClient(conn net.Conn, config *model.Config) (*smtp.Client, *model.Ap } if *config.EmailSettings.EnableSMTPAuth { - auth := smtp.PlainAuth("", config.EmailSettings.SMTPUsername, config.EmailSettings.SMTPPassword, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) + var auth smtp.Auth + if _, ok := conn.(*tls.Conn); ok { + auth = PlainAuthOverTLSConn("", config.EmailSettings.SMTPUsername, config.EmailSettings.SMTPPassword, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) + } else { + auth = smtp.PlainAuth("", config.EmailSettings.SMTPUsername, config.EmailSettings.SMTPPassword, config.EmailSettings.SMTPServer+":"+config.EmailSettings.SMTPPort) + } if err = c.Auth(auth); err != nil { return nil, model.NewAppError("SendMail", "utils.mail.new_client.auth.app_error", nil, err.Error(), http.StatusInternalServerError) |