summaryrefslogtreecommitdiffstats
path: root/api4
diff options
context:
space:
mode:
authorJoram Wilander <jwawilander@gmail.com>2018-05-16 13:43:22 -0400
committerGitHub <noreply@github.com>2018-05-16 13:43:22 -0400
commit1f6c271b3bedd6656ae7155714423b1b39a669c1 (patch)
tree9ce6390c237cc5f7c16d63addb4372033807cff8 /api4
parent02f8c18f40cd0e973e4c75b751e8fcbbbd019728 (diff)
downloadchat-1f6c271b3bedd6656ae7155714423b1b39a669c1.tar.gz
chat-1f6c271b3bedd6656ae7155714423b1b39a669c1.tar.bz2
chat-1f6c271b3bedd6656ae7155714423b1b39a669c1.zip
MM-8708 Remove api package (#8784)
* Remove api package * Remove api dependency from cmd package * Remove EnableAPIv3 setting * Update web tests * Add more websocket tests * Move some ws and oauth tests to api4 package * Move command tests into api4 package * Test fixes * Fix msg command test * Add some app file tests
Diffstat (limited to 'api4')
-rw-r--r--api4/api.go7
-rw-r--r--api4/apitestlib.go2
-rw-r--r--api4/commands_test.go449
-rw-r--r--api4/oauth_test.go413
-rw-r--r--api4/websocket_test.go387
5 files changed, 1252 insertions, 6 deletions
diff --git a/api4/api.go b/api4/api.go
index 9172391dd..918154c0d 100644
--- a/api4/api.go
+++ b/api4/api.go
@@ -113,7 +113,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{},
@@ -231,10 +231,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
}
diff --git a/api4/apitestlib.go b/api4/apitestlib.go
index 0ce334154..86150e05a 100644
--- a/api4/apitestlib.go
+++ b/api4/apitestlib.go
@@ -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()
diff --git a/api4/commands_test.go b/api4/commands_test.go
new file mode 100644
index 000000000..cb960c608
--- /dev/null
+++ b/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)
+ }
+}
diff --git a/api4/oauth_test.go b/api4/oauth_test.go
index 0862f13f5..8cf20ca5e 100644
--- a/api4/oauth_test.go
+++ b/api4/oauth_test.go
@@ -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
+}
diff --git a/api4/websocket_test.go b/api4/websocket_test.go
index fa40629dc..9e4c1da73 100644
--- a/api4/websocket_test.go
+++ b/api4/websocket_test.go
@@ -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()
+}