diff options
Diffstat (limited to 'api4/websocket_test.go')
-rw-r--r-- | api4/websocket_test.go | 387 |
1 files changed, 387 insertions, 0 deletions
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() +} |