Merge remote-tracking branch 'origin/master' into advanced-permissions-phase-2
Этот коммит содержится в:
@@ -114,7 +114,7 @@ type API struct {
|
||||
BaseRoutes *Routes
|
||||
}
|
||||
|
||||
func Init(a *app.App, root *mux.Router, full bool) *API {
|
||||
func Init(a *app.App, root *mux.Router) *API {
|
||||
api := &API{
|
||||
App: a,
|
||||
BaseRoutes: &Routes{},
|
||||
@@ -234,10 +234,7 @@ func Init(a *app.App, root *mux.Router, full bool) *API {
|
||||
|
||||
root.Handle("/api/v4/{anything:.*}", http.HandlerFunc(api.Handle404))
|
||||
|
||||
// REMOVE CONDITION WHEN APIv3 REMOVED
|
||||
if full {
|
||||
a.InitEmailBatching()
|
||||
}
|
||||
a.InitEmailBatching()
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func setupTestHelper(enterprise bool) *TestHelper {
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = prevListenAddress })
|
||||
Init(th.App, th.App.Srv.Router, true)
|
||||
Init(th.App, th.App.Srv.Router)
|
||||
web.NewWeb(th.App, th.App.Srv.Router)
|
||||
wsapi.Init(th.App, th.App.Srv.WebSocketRouter)
|
||||
th.App.Srv.Store.MarkSystemRanUnitTests()
|
||||
|
||||
449
api4/commands_test.go
Обычный файл
449
api4/commands_test.go
Обычный файл
@@ -0,0 +1,449 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
package api4
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
func TestEchoCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel1 := th.BasicChannel
|
||||
|
||||
echoTestString := "/echo test"
|
||||
|
||||
if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, echoTestString)).(*model.CommandResponse); r1 == nil {
|
||||
t.Fatal("Echo command failed to execute")
|
||||
}
|
||||
|
||||
if r1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/echo ")).(*model.CommandResponse); r1 == nil {
|
||||
t.Fatal("Echo command failed to execute")
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
p1 := Client.Must(Client.GetPostsForChannel(channel1.Id, 0, 2, "")).(*model.PostList)
|
||||
if len(p1.Order) != 2 {
|
||||
t.Fatal("Echo command failed to send")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupmsgCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
team := th.BasicTeam
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
user3 := th.CreateUser()
|
||||
user4 := th.CreateUser()
|
||||
user5 := th.CreateUser()
|
||||
user6 := th.CreateUser()
|
||||
user7 := th.CreateUser()
|
||||
user8 := th.CreateUser()
|
||||
user9 := th.CreateUser()
|
||||
th.LinkUserToTeam(user3, team)
|
||||
th.LinkUserToTeam(user4, team)
|
||||
|
||||
rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse)
|
||||
|
||||
group1 := model.GetGroupNameFromUserIds([]string{user1.Id, user2.Id, user3.Id})
|
||||
|
||||
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+group1) {
|
||||
t.Fatal("failed to create group channel")
|
||||
}
|
||||
|
||||
rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user3.Username+","+user4.Username+" foobar")).(*model.CommandResponse)
|
||||
group2 := model.GetGroupNameFromUserIds([]string{user1.Id, user3.Id, user4.Id})
|
||||
|
||||
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+group2) {
|
||||
t.Fatal("failed to create second direct channel")
|
||||
}
|
||||
if result := Client.Must(Client.SearchPosts(team.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 {
|
||||
t.Fatal("post did not get sent to direct message")
|
||||
}
|
||||
|
||||
rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username)).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+group1) {
|
||||
t.Fatal("failed to go back to existing group channel")
|
||||
}
|
||||
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+" foobar"))
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg "+user2.Username+","+user3.Username+","+user4.Username+","+user5.Username+","+user6.Username+","+user7.Username+","+user8.Username+","+user9.Username+" foobar"))
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk foobar"))
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/groupmsg junk,junk2 foobar"))
|
||||
}
|
||||
|
||||
func TestInvitePeopleCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test@example.com")).(*model.CommandResponse)
|
||||
if r1 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
r2 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people test1@example.com test2@example.com")).(*model.CommandResponse)
|
||||
if r2 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
r3 := Client.Must(Client.ExecuteCommand(channel.Id, "/invite_people")).(*model.CommandResponse)
|
||||
if r3 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
}
|
||||
|
||||
// also used to test /open (see command_open_test.go)
|
||||
func testJoinCommands(t *testing.T, alias string) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
team := th.BasicTeam
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel0 := &model.Channel{DisplayName: "00", Name: "00" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel0 = Client.Must(Client.CreateChannel(channel0)).(*model.Channel)
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel)
|
||||
Client.Must(Client.RemoveUserFromChannel(channel1.Id, th.BasicUser.Id))
|
||||
|
||||
channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel)
|
||||
Client.Must(Client.RemoveUserFromChannel(channel2.Id, th.BasicUser.Id))
|
||||
|
||||
channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel)
|
||||
|
||||
rs5 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel2.Name)).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs5.GotoLocation, "/"+team.Name+"/channels/"+channel2.Name) {
|
||||
t.Fatal("failed to join channel")
|
||||
}
|
||||
|
||||
rs6 := Client.Must(Client.ExecuteCommand(channel0.Id, "/"+alias+" "+channel3.Name)).(*model.CommandResponse)
|
||||
if strings.HasSuffix(rs6.GotoLocation, "/"+team.Name+"/channels/"+channel3.Name) {
|
||||
t.Fatal("should not have joined direct message channel")
|
||||
}
|
||||
|
||||
c1 := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel)
|
||||
|
||||
found := false
|
||||
for _, c := range c1 {
|
||||
if c.Id == channel2.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("did not join channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinCommands(t *testing.T) {
|
||||
testJoinCommands(t, "join")
|
||||
}
|
||||
|
||||
func TestLoadTestHelpCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true })
|
||||
|
||||
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test help")).(*model.CommandResponse)
|
||||
if !strings.Contains(rs.Text, "Mattermost testing commands to help") {
|
||||
t.Fatal(rs.Text)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestLoadTestSetupCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true })
|
||||
|
||||
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test setup fuzz 1 1 1")).(*model.CommandResponse)
|
||||
if rs.Text != "Created environment" {
|
||||
t.Fatal(rs.Text)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestLoadTestUsersCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true })
|
||||
|
||||
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test users fuzz 1 2")).(*model.CommandResponse)
|
||||
if rs.Text != "Added users" {
|
||||
t.Fatal(rs.Text)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestLoadTestChannelsCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true })
|
||||
|
||||
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test channels fuzz 1 2")).(*model.CommandResponse)
|
||||
if rs.Text != "Added channels" {
|
||||
t.Fatal(rs.Text)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestLoadTestPostsCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableTesting = true })
|
||||
|
||||
rs := Client.Must(Client.ExecuteCommand(channel.Id, "/test posts fuzz 2 3 2")).(*model.CommandResponse)
|
||||
if rs.Text != "Added posts" {
|
||||
t.Fatal(rs.Text)
|
||||
}
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestLeaveCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
team := th.BasicTeam
|
||||
user2 := th.BasicUser2
|
||||
|
||||
channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
|
||||
channel1 = Client.Must(Client.CreateChannel(channel1)).(*model.Channel)
|
||||
Client.Must(Client.AddChannelMember(channel1.Id, th.BasicUser.Id))
|
||||
|
||||
channel2 := &model.Channel{DisplayName: "BB", Name: "bb" + model.NewId() + "a", Type: model.CHANNEL_PRIVATE, TeamId: team.Id}
|
||||
channel2 = Client.Must(Client.CreateChannel(channel2)).(*model.Channel)
|
||||
Client.Must(Client.AddChannelMember(channel2.Id, th.BasicUser.Id))
|
||||
Client.Must(Client.AddChannelMember(channel2.Id, user2.Id))
|
||||
|
||||
channel3 := Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id)).(*model.Channel)
|
||||
|
||||
rs1 := Client.Must(Client.ExecuteCommand(channel1.Id, "/leave")).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) {
|
||||
t.Fatal("failed to leave open channel 1")
|
||||
}
|
||||
|
||||
rs2 := Client.Must(Client.ExecuteCommand(channel2.Id, "/leave")).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+model.DEFAULT_CHANNEL) {
|
||||
t.Fatal("failed to leave private channel 1")
|
||||
}
|
||||
|
||||
_, err := Client.ExecuteCommand(channel3.Id, "/leave")
|
||||
if err == nil {
|
||||
t.Fatal("should fail leaving direct channel")
|
||||
}
|
||||
|
||||
cdata := Client.Must(Client.GetChannelsForTeamForUser(th.BasicTeam.Id, th.BasicUser.Id, "")).([]*model.Channel)
|
||||
|
||||
found := false
|
||||
for _, c := range cdata {
|
||||
if c.Id == channel1.Id || c.Id == channel2.Id {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
t.Fatal("did not leave right channels")
|
||||
}
|
||||
|
||||
for _, c := range cdata {
|
||||
if c.Name == model.DEFAULT_CHANNEL {
|
||||
if _, err := Client.RemoveUserFromChannel(c.Id, th.BasicUser.Id); err == nil {
|
||||
t.Fatal("should have errored on leaving default channel")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogoutTestCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/logout"))
|
||||
}
|
||||
|
||||
func TestMeCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
testString := "/me hello"
|
||||
|
||||
r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse)
|
||||
if r1 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList)
|
||||
if len(p1.Order) != 2 {
|
||||
t.Fatal("Command failed to send")
|
||||
} else {
|
||||
if p1.Posts[p1.Order[0]].Message != `*hello*` {
|
||||
t.Log(p1.Posts[p1.Order[0]].Message)
|
||||
t.Fatal("invalid shrug response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
team := th.BasicTeam
|
||||
user1 := th.BasicUser
|
||||
user2 := th.BasicUser2
|
||||
user3 := th.CreateUser()
|
||||
th.LinkUserToTeam(user3, team)
|
||||
|
||||
Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user2.Id))
|
||||
Client.Must(Client.CreateDirectChannel(th.BasicUser.Id, user3.Id))
|
||||
|
||||
rs1 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs1.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
|
||||
t.Fatal("failed to create direct channel")
|
||||
}
|
||||
|
||||
rs2 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user3.Username+" foobar")).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user3.Id) && !strings.HasSuffix(rs2.GotoLocation, "/"+team.Name+"/channels/"+user3.Id+"__"+user1.Id) {
|
||||
t.Fatal("failed to create second direct channel")
|
||||
}
|
||||
if result := Client.Must(Client.SearchPosts(th.BasicTeam.Id, "foobar", false)).(*model.PostList); len(result.Order) == 0 {
|
||||
t.Fatalf("post did not get sent to direct message")
|
||||
}
|
||||
|
||||
rs3 := Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+user2.Username)).(*model.CommandResponse)
|
||||
if !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user1.Id+"__"+user2.Id) && !strings.HasSuffix(rs3.GotoLocation, "/"+team.Name+"/channels/"+user2.Id+"__"+user1.Id) {
|
||||
t.Fatal("failed to go back to existing direct channel")
|
||||
}
|
||||
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg "+th.BasicUser.Username+" foobar"))
|
||||
Client.Must(Client.ExecuteCommand(th.BasicChannel.Id, "/msg junk foobar"))
|
||||
}
|
||||
|
||||
func TestOpenCommands(t *testing.T) {
|
||||
testJoinCommands(t, "open")
|
||||
}
|
||||
|
||||
func TestSearchCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/search"))
|
||||
}
|
||||
|
||||
func TestSettingsCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/settings"))
|
||||
}
|
||||
|
||||
func TestShortcutsCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
th.Client.Must(th.Client.ExecuteCommand(th.BasicChannel.Id, "/shortcuts"))
|
||||
}
|
||||
|
||||
func TestShrugCommand(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
|
||||
testString := "/shrug"
|
||||
|
||||
r1 := Client.Must(Client.ExecuteCommand(channel.Id, testString)).(*model.CommandResponse)
|
||||
if r1 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
p1 := Client.Must(Client.GetPostsForChannel(channel.Id, 0, 2, "")).(*model.PostList)
|
||||
if len(p1.Order) != 2 {
|
||||
t.Fatal("Command failed to send")
|
||||
} else {
|
||||
if p1.Posts[p1.Order[0]].Message != `¯\\\_(ツ)\_/¯` {
|
||||
t.Log(p1.Posts[p1.Order[0]].Message)
|
||||
t.Fatal("invalid shrug response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCommands(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
commandAndTest(t, th, "away")
|
||||
commandAndTest(t, th, "offline")
|
||||
commandAndTest(t, th, "online")
|
||||
}
|
||||
|
||||
func commandAndTest(t *testing.T, th *TestHelper, status string) {
|
||||
Client := th.Client
|
||||
channel := th.BasicChannel
|
||||
user := th.BasicUser
|
||||
|
||||
r1 := Client.Must(Client.ExecuteCommand(channel.Id, "/"+status)).(*model.CommandResponse)
|
||||
if r1 == nil {
|
||||
t.Fatal("Command failed to execute")
|
||||
}
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
rstatus := Client.Must(Client.GetUserStatus(user.Id, "")).(*model.Status)
|
||||
|
||||
if rstatus.Status != status {
|
||||
t.Fatal("Error setting status " + status)
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,19 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
)
|
||||
|
||||
func TestCreateOAuthApp(t *testing.T) {
|
||||
@@ -726,3 +733,409 @@ func TestDeauthorizeOAuthApp(t *testing.T) {
|
||||
_, resp = Client.DeauthorizeOAuthApp(rapp.Id)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestOAuthAccessToken(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
|
||||
oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = false })
|
||||
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Log(resp.StatusCode)
|
||||
t.Fatal("should have failed - oauth providing turned off")
|
||||
}
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
Client.Logout()
|
||||
|
||||
data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad grant type")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client id")
|
||||
}
|
||||
data.Set("client_id", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client id")
|
||||
}
|
||||
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad client secret")
|
||||
}
|
||||
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", "")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - missing code")
|
||||
}
|
||||
|
||||
data.Set("code", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad code")
|
||||
}
|
||||
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", "junk")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - non-matching redirect uri")
|
||||
}
|
||||
|
||||
// reset data for successful request
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
|
||||
token := ""
|
||||
refreshToken := ""
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else {
|
||||
token = rsp.AccessToken
|
||||
refreshToken = rsp.RefreshToken
|
||||
}
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := Client.DoApiGet("/users?page=0&per_page=100&access_token="+token, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil {
|
||||
t.Fatal("should have failed - no access token provided")
|
||||
}
|
||||
|
||||
if _, resp := Client.GetUsers(0, 100, ""); resp.Error == nil {
|
||||
t.Fatal("should have failed - bad access token provided")
|
||||
}
|
||||
|
||||
Client.SetOAuthToken(token)
|
||||
if users, resp := Client.GetUsers(0, 100, ""); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(users) == 0 {
|
||||
t.Fatal("users empty - did not get results correctly")
|
||||
}
|
||||
}
|
||||
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("should have failed - tried to reuse auth code")
|
||||
}
|
||||
|
||||
data.Set("grant_type", model.REFRESH_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("refresh_token", "")
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Del("code")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - refresh token empty")
|
||||
}
|
||||
|
||||
data.Set("refresh_token", refreshToken)
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
Client.SetOAuthToken(rsp.AccessToken)
|
||||
_, resp = Client.GetMe("")
|
||||
if resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
|
||||
data.Set("refresh_token", rsp.RefreshToken)
|
||||
}
|
||||
|
||||
if rsp, resp := Client.GetOAuthAccessToken(data); resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if len(rsp.AccessToken) == 0 {
|
||||
t.Fatal("access token not returned")
|
||||
} else if len(rsp.RefreshToken) == 0 {
|
||||
t.Fatal("refresh token not returned")
|
||||
} else if rsp.RefreshToken == refreshToken {
|
||||
t.Fatal("refresh token did not update")
|
||||
}
|
||||
|
||||
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
|
||||
t.Fatal("access token type incorrect")
|
||||
}
|
||||
Client.SetOAuthToken(rsp.AccessToken)
|
||||
_, resp = Client.GetMe("")
|
||||
if resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
}
|
||||
}
|
||||
|
||||
authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1}
|
||||
<-th.App.Srv.Store.OAuth().SaveAuthData(authData)
|
||||
|
||||
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Set("code", authData.Code)
|
||||
data.Del("refresh_token")
|
||||
if _, resp := Client.GetOAuthAccessToken(data); resp.Error == nil {
|
||||
t.Fatal("Should have failed - code is expired")
|
||||
}
|
||||
|
||||
Client.ClearOAuthToken()
|
||||
}
|
||||
|
||||
func TestOAuthComplete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
|
||||
r, err := HttpGet(Client.Url+"/login/gitlab/complete?code=123", Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = true })
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = model.NewId() })
|
||||
|
||||
stateProps := map[string]string{}
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
stateProps["team_id"] = th.BasicTeam.Id
|
||||
stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint
|
||||
|
||||
state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id)
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
r, err = HttpGet(Client.Url+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), Client.HttpClient, "", true)
|
||||
assert.NotNil(t, err)
|
||||
closeBody(r)
|
||||
|
||||
// We are going to use mattermost as the provider emulating gitlab
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.TEAM_USER_ROLE_ID)
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{
|
||||
Client.Url + "/signup/" + model.SERVICE_GITLAB + "/complete",
|
||||
Client.Url + "/login/" + model.SERVICE_GITLAB + "/complete",
|
||||
},
|
||||
IsTrusted: true,
|
||||
}
|
||||
oauthApp = Client.Must(Client.CreateOAuthApp(oauthApp)).(*model.OAuthApp)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = oauthApp.Id })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = oauthApp.ClientSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = Client.Url + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = Client.Url + "/oauth/access_token" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserApiEndpoint = Client.ApiUrl + "/users/me" })
|
||||
|
||||
provider := &MattermostTestProvider{}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AUTHCODE_RESPONSE_TYPE,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectUri: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, resp := Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
code := rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO
|
||||
delete(stateProps, "team_id")
|
||||
stateProps["redirect_to"] = th.App.Config().GitLabSettings.AuthEndpoint
|
||||
stateProps["hash"] = utils.HashSha256(th.App.Config().GitLabSettings.Id)
|
||||
stateProps["redirect_to"] = "/oauth/authorize"
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
einterfaces.RegisterOauthProvider(model.SERVICE_GITLAB, provider)
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
if result := <-th.App.Srv.Store.User().UpdateAuthData(
|
||||
th.BasicUser.Id, model.SERVICE_GITLAB, &th.BasicUser.Email, th.BasicUser.Email, true); result.Err != nil {
|
||||
t.Fatal(result.Err)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_LOGIN
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
delete(stateProps, "action")
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, resp = Client.AuthorizeOAuthApp(authRequest)
|
||||
CheckNoError(t, resp)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAUTH_ACTION_SIGNUP
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps)))
|
||||
if r, err := HttpGet(Client.Url+"/login/"+model.SERVICE_GITLAB+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), Client.HttpClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
}
|
||||
|
||||
func HttpGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, *model.AppError) {
|
||||
rq, _ := http.NewRequest("GET", url, nil)
|
||||
rq.Close = true
|
||||
|
||||
if len(authToken) > 0 {
|
||||
rq.Header.Set(model.HEADER_AUTH, authToken)
|
||||
}
|
||||
|
||||
if !followRedirect {
|
||||
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
|
||||
if rp, err := httpClient.Do(rq); err != nil {
|
||||
return nil, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode == 307 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
defer closeBody(rp)
|
||||
return rp, model.AppErrorFromJson(rp.Body)
|
||||
} else {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
if r != nil && r.Body != nil {
|
||||
ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type MattermostTestProvider struct {
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetIdentifier() string {
|
||||
return model.SERVICE_GITLAB
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetUserFromJson(data io.Reader) *model.User {
|
||||
return model.UserFromJson(data)
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetAuthDataFromJson(data io.Reader) string {
|
||||
authData := model.UserFromJson(data)
|
||||
return authData.Email
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
package api4
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/store"
|
||||
)
|
||||
|
||||
func TestWebSocket(t *testing.T) {
|
||||
@@ -71,3 +77,384 @@ func TestWebSocket(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebSocketEvent(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
WebSocketClient, err := th.CreateWebSocketClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer WebSocketClient.Close()
|
||||
|
||||
WebSocketClient.Listen()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
|
||||
t.Fatal("should have responded OK to authentication challenge")
|
||||
}
|
||||
|
||||
omitUser := make(map[string]bool, 1)
|
||||
omitUser["somerandomid"] = true
|
||||
evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser)
|
||||
evt1.Add("user_id", "somerandomid")
|
||||
th.App.Publish(evt1)
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
stop := make(chan bool)
|
||||
eventHit := false
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" {
|
||||
eventHit = true
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
|
||||
stop <- true
|
||||
|
||||
if !eventHit {
|
||||
t.Fatal("did not receive typing event")
|
||||
}
|
||||
|
||||
evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil)
|
||||
th.App.Publish(evt2)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
eventHit = false
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_TYPING {
|
||||
eventHit = true
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(400 * time.Millisecond)
|
||||
|
||||
stop <- true
|
||||
|
||||
if eventHit {
|
||||
t.Fatal("got typing event for bad channel id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDirectChannelWithSocket(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
user2 := th.BasicUser2
|
||||
|
||||
users := make([]*model.User, 0)
|
||||
users = append(users, user2)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
users = append(users, th.CreateUser())
|
||||
}
|
||||
|
||||
WebSocketClient, err := th.CreateWebSocketClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer WebSocketClient.Close()
|
||||
WebSocketClient.Listen()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
|
||||
t.Fatal("should have responded OK to authentication challenge")
|
||||
}
|
||||
|
||||
wsr := <-WebSocketClient.EventChannel
|
||||
if wsr.Event != model.WEBSOCKET_EVENT_HELLO {
|
||||
t.Fatal("missing hello")
|
||||
}
|
||||
|
||||
stop := make(chan bool)
|
||||
count := 0
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case wsr := <-WebSocketClient.EventChannel:
|
||||
if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED {
|
||||
count = count + 1
|
||||
}
|
||||
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for _, user := range users {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if _, resp := Client.CreateDirectChannel(th.BasicUser.Id, user.Id); resp.Error != nil {
|
||||
t.Fatal("failed to create DM channel")
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
|
||||
stop <- true
|
||||
|
||||
if count != len(users) {
|
||||
t.Fatal("We didn't get the proper amount of direct_added messages")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestWebsocketOriginSecurity(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port)
|
||||
|
||||
// Should fail because origin doesn't match
|
||||
_, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{"http://www.evil.com"},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!")
|
||||
}
|
||||
|
||||
// We are not a browser so we can spoof this just fine
|
||||
_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should succeed now because open CORS
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" })
|
||||
_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{"http://www.evil.com"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should succeed now because matching CORS
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" })
|
||||
_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{"http://www.evil.com"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Should fail because non-matching CORS
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" })
|
||||
_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{"http://www.evil.com"},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should have errored because Origin contain AllowCorsFrom")
|
||||
}
|
||||
|
||||
// Should fail because non-matching CORS
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" })
|
||||
_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{
|
||||
"Origin": []string{"http://www.good.co"},
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!")
|
||||
}
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" })
|
||||
}
|
||||
|
||||
func TestWebSocketStatuses(t *testing.T) {
|
||||
th := Setup().InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Client := th.Client
|
||||
WebSocketClient, err := th.CreateWebSocketClient()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer WebSocketClient.Close()
|
||||
WebSocketClient.Listen()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
|
||||
t.Fatal("should have responded OK to authentication challenge")
|
||||
}
|
||||
|
||||
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
|
||||
rteam, _ := Client.CreateTeam(&team)
|
||||
|
||||
user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
ruser := Client.Must(Client.CreateUser(&user)).(*model.User)
|
||||
th.LinkUserToTeam(ruser, rteam)
|
||||
store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id))
|
||||
|
||||
user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"}
|
||||
ruser2 := Client.Must(Client.CreateUser(&user2)).(*model.User)
|
||||
th.LinkUserToTeam(ruser2, rteam)
|
||||
store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id))
|
||||
|
||||
Client.Login(user.Email, user.Password)
|
||||
|
||||
th.LoginBasic2()
|
||||
|
||||
WebSocketClient2, err2 := th.CreateWebSocketClient()
|
||||
if err2 != nil {
|
||||
t.Fatal(err2)
|
||||
}
|
||||
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
WebSocketClient.GetStatuses()
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
for _, status := range resp.Data {
|
||||
if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE && status != model.STATUS_DND {
|
||||
t.Fatalf("one of the statuses had an invalid value status=%v", status)
|
||||
}
|
||||
}
|
||||
|
||||
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||
t.Log(resp.Data)
|
||||
t.Fatal("should have had user status")
|
||||
} else if status != model.STATUS_ONLINE {
|
||||
t.Log(status)
|
||||
t.Fatal("status should have been online")
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
for _, status := range resp.Data {
|
||||
if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE {
|
||||
t.Fatal("one of the statuses had an invalid value")
|
||||
}
|
||||
}
|
||||
|
||||
if status, ok := resp.Data[th.BasicUser2.Id]; !ok {
|
||||
t.Log(len(resp.Data))
|
||||
t.Fatal("should have had user status")
|
||||
} else if status != model.STATUS_ONLINE {
|
||||
t.Log(status)
|
||||
t.Fatal("status should have been online")
|
||||
} else if len(resp.Data) != 1 {
|
||||
t.Fatal("only 1 status should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
if len(resp.Data) != 2 {
|
||||
t.Fatal("2 statuses should be returned")
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient.GetStatusesByIds([]string{})
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
t.Fatal("should have errored - empty user ids")
|
||||
}
|
||||
|
||||
WebSocketClient2.Close()
|
||||
|
||||
th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||
|
||||
awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 })
|
||||
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false)
|
||||
th.App.SetStatusOnline(th.BasicUser.Id, "junk", false)
|
||||
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
|
||||
WebSocketClient.GetStatuses()
|
||||
if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil {
|
||||
t.Fatal(resp.Error)
|
||||
} else {
|
||||
if resp.SeqReply != WebSocketClient.Sequence-1 {
|
||||
t.Fatal("bad sequence number")
|
||||
}
|
||||
|
||||
if _, ok := resp.Data[th.BasicUser2.Id]; ok {
|
||||
t.Fatal("should not have had user status")
|
||||
}
|
||||
}
|
||||
|
||||
stop := make(chan bool)
|
||||
onlineHit := false
|
||||
awayHit := false
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case resp := <-WebSocketClient.EventChannel:
|
||||
if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id {
|
||||
status := resp.Data["status"].(string)
|
||||
if status == model.STATUS_ONLINE {
|
||||
onlineHit = true
|
||||
} else if status == model.STATUS_AWAY {
|
||||
awayHit = true
|
||||
}
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
stop <- true
|
||||
|
||||
if !onlineHit {
|
||||
t.Fatal("didn't get online event")
|
||||
}
|
||||
if !awayHit {
|
||||
t.Fatal("didn't get away event")
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
WebSocketClient.Close()
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user