github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/api4/websocket_test.go (about) 1 // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package api4 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/gorilla/websocket" 14 "github.com/stretchr/testify/require" 15 16 "github.com/xzl8028/xenia-server/model" 17 ) 18 19 func TestWebSocket(t *testing.T) { 20 th := Setup().InitBasic() 21 defer th.TearDown() 22 WebSocketClient, err := th.CreateWebSocketClient() 23 if err != nil { 24 t.Fatal(err) 25 } 26 defer WebSocketClient.Close() 27 28 time.Sleep(300 * time.Millisecond) 29 30 // Test closing and reconnecting 31 WebSocketClient.Close() 32 if err := WebSocketClient.Connect(); err != nil { 33 t.Fatal(err) 34 } 35 36 WebSocketClient.Listen() 37 38 time.Sleep(300 * time.Millisecond) 39 if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { 40 t.Fatal("should have responded OK to authentication challenge") 41 } 42 43 WebSocketClient.SendMessage("ping", nil) 44 time.Sleep(300 * time.Millisecond) 45 if resp := <-WebSocketClient.ResponseChannel; resp.Data["text"].(string) != "pong" { 46 t.Fatal("wrong response") 47 } 48 49 WebSocketClient.SendMessage("", nil) 50 time.Sleep(300 * time.Millisecond) 51 if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.no_action.app_error" { 52 t.Fatal("should have been no action response") 53 } 54 55 WebSocketClient.SendMessage("junk", nil) 56 time.Sleep(300 * time.Millisecond) 57 if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_action.app_error" { 58 t.Fatal("should have been bad action response") 59 } 60 61 req := &model.WebSocketRequest{} 62 req.Seq = 0 63 req.Action = "ping" 64 WebSocketClient.Conn.WriteJSON(req) 65 time.Sleep(300 * time.Millisecond) 66 if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_seq.app_error" { 67 t.Fatal("should have been bad action response") 68 } 69 70 WebSocketClient.UserTyping("", "") 71 time.Sleep(300 * time.Millisecond) 72 if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.websocket_handler.invalid_param.app_error" { 73 t.Fatal("should have been invalid param response") 74 } else { 75 if resp.Error.DetailedError != "" { 76 t.Fatal("detailed error not cleared") 77 } 78 } 79 } 80 81 func TestWebSocketEvent(t *testing.T) { 82 th := Setup().InitBasic() 83 defer th.TearDown() 84 85 WebSocketClient, err := th.CreateWebSocketClient() 86 if err != nil { 87 t.Fatal(err) 88 } 89 defer WebSocketClient.Close() 90 91 WebSocketClient.Listen() 92 93 time.Sleep(300 * time.Millisecond) 94 if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { 95 t.Fatal("should have responded OK to authentication challenge") 96 } 97 98 omitUser := make(map[string]bool, 1) 99 omitUser["somerandomid"] = true 100 evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser) 101 evt1.Add("user_id", "somerandomid") 102 th.App.Publish(evt1) 103 104 time.Sleep(300 * time.Millisecond) 105 106 stop := make(chan bool) 107 eventHit := false 108 109 go func() { 110 for { 111 select { 112 case resp := <-WebSocketClient.EventChannel: 113 if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { 114 eventHit = true 115 } 116 case <-stop: 117 return 118 } 119 } 120 }() 121 122 time.Sleep(400 * time.Millisecond) 123 124 stop <- true 125 126 if !eventHit { 127 t.Fatal("did not receive typing event") 128 } 129 130 evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil) 131 th.App.Publish(evt2) 132 time.Sleep(300 * time.Millisecond) 133 134 eventHit = false 135 136 go func() { 137 for { 138 select { 139 case resp := <-WebSocketClient.EventChannel: 140 if resp.Event == model.WEBSOCKET_EVENT_TYPING { 141 eventHit = true 142 } 143 case <-stop: 144 return 145 } 146 } 147 }() 148 149 time.Sleep(400 * time.Millisecond) 150 151 stop <- true 152 153 if eventHit { 154 t.Fatal("got typing event for bad channel id") 155 } 156 } 157 158 func TestCreateDirectChannelWithSocket(t *testing.T) { 159 th := Setup().InitBasic() 160 defer th.TearDown() 161 162 Client := th.Client 163 user2 := th.BasicUser2 164 165 users := make([]*model.User, 0) 166 users = append(users, user2) 167 168 for i := 0; i < 10; i++ { 169 users = append(users, th.CreateUser()) 170 } 171 172 WebSocketClient, err := th.CreateWebSocketClient() 173 if err != nil { 174 t.Fatal(err) 175 } 176 defer WebSocketClient.Close() 177 WebSocketClient.Listen() 178 179 time.Sleep(300 * time.Millisecond) 180 if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { 181 t.Fatal("should have responded OK to authentication challenge") 182 } 183 184 wsr := <-WebSocketClient.EventChannel 185 if wsr.Event != model.WEBSOCKET_EVENT_HELLO { 186 t.Fatal("missing hello") 187 } 188 189 stop := make(chan bool) 190 count := 0 191 192 go func() { 193 for { 194 select { 195 case wsr := <-WebSocketClient.EventChannel: 196 if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { 197 count = count + 1 198 } 199 200 case <-stop: 201 return 202 } 203 } 204 }() 205 206 for _, user := range users { 207 time.Sleep(100 * time.Millisecond) 208 if _, resp := Client.CreateDirectChannel(th.BasicUser.Id, user.Id); resp.Error != nil { 209 t.Fatal("failed to create DM channel") 210 } 211 } 212 213 time.Sleep(5000 * time.Millisecond) 214 215 stop <- true 216 217 if count != len(users) { 218 t.Fatal("We didn't get the proper amount of direct_added messages") 219 } 220 221 } 222 223 func TestWebsocketOriginSecurity(t *testing.T) { 224 th := Setup().InitBasic() 225 defer th.TearDown() 226 227 url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port) 228 229 // Should fail because origin doesn't match 230 _, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 231 "Origin": []string{"http://www.evil.com"}, 232 }) 233 if err == nil { 234 t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") 235 } 236 237 // We are not a browser so we can spoof this just fine 238 _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 239 "Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)}, 240 }) 241 if err != nil { 242 t.Fatal(err) 243 } 244 245 // Should succeed now because open CORS 246 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" }) 247 _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 248 "Origin": []string{"http://www.evil.com"}, 249 }) 250 if err != nil { 251 t.Fatal(err) 252 } 253 254 // Should succeed now because matching CORS 255 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" }) 256 _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 257 "Origin": []string{"http://www.evil.com"}, 258 }) 259 if err != nil { 260 t.Fatal(err) 261 } 262 263 // Should fail because non-matching CORS 264 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) 265 _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 266 "Origin": []string{"http://www.evil.com"}, 267 }) 268 if err == nil { 269 t.Fatal("Should have errored because Origin contain AllowCorsFrom") 270 } 271 272 // Should fail because non-matching CORS 273 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" }) 274 _, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX+"/websocket", http.Header{ 275 "Origin": []string{"http://www.good.co"}, 276 }) 277 if err == nil { 278 t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!") 279 } 280 281 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" }) 282 } 283 284 func TestWebSocketStatuses(t *testing.T) { 285 th := Setup().InitBasic() 286 defer th.TearDown() 287 288 Client := th.Client 289 WebSocketClient, err := th.CreateWebSocketClient() 290 if err != nil { 291 t.Fatal(err) 292 } 293 defer WebSocketClient.Close() 294 WebSocketClient.Listen() 295 296 time.Sleep(300 * time.Millisecond) 297 if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK { 298 t.Fatal("should have responded OK to authentication challenge") 299 } 300 301 team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} 302 rteam, _ := Client.CreateTeam(&team) 303 304 user := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} 305 ruser := Client.Must(Client.CreateUser(&user)).(*model.User) 306 th.LinkUserToTeam(ruser, rteam) 307 _, err = th.App.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email) 308 require.Nil(t, err) 309 310 user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} 311 ruser2 := Client.Must(Client.CreateUser(&user2)).(*model.User) 312 th.LinkUserToTeam(ruser2, rteam) 313 _, err = th.App.Srv.Store.User().VerifyEmail(ruser2.Id, ruser2.Email) 314 require.Nil(t, err) 315 316 Client.Login(user.Email, user.Password) 317 318 th.LoginBasic2() 319 320 WebSocketClient2, err2 := th.CreateWebSocketClient() 321 if err2 != nil { 322 t.Fatal(err2) 323 } 324 325 time.Sleep(1000 * time.Millisecond) 326 327 WebSocketClient.GetStatuses() 328 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 329 t.Fatal(resp.Error) 330 } else { 331 if resp.SeqReply != WebSocketClient.Sequence-1 { 332 t.Fatal("bad sequence number") 333 } 334 335 for _, status := range resp.Data { 336 if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE && status != model.STATUS_DND { 337 t.Fatalf("one of the statuses had an invalid value status=%v", status) 338 } 339 } 340 341 if status, ok := resp.Data[th.BasicUser2.Id]; !ok { 342 t.Log(resp.Data) 343 t.Fatal("should have had user status") 344 } else if status != model.STATUS_ONLINE { 345 t.Log(status) 346 t.Fatal("status should have been online") 347 } 348 } 349 350 WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id}) 351 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 352 t.Fatal(resp.Error) 353 } else { 354 if resp.SeqReply != WebSocketClient.Sequence-1 { 355 t.Fatal("bad sequence number") 356 } 357 358 for _, status := range resp.Data { 359 if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { 360 t.Fatal("one of the statuses had an invalid value") 361 } 362 } 363 364 if status, ok := resp.Data[th.BasicUser2.Id]; !ok { 365 t.Log(len(resp.Data)) 366 t.Fatal("should have had user status") 367 } else if status != model.STATUS_ONLINE { 368 t.Log(status) 369 t.Fatal("status should have been online") 370 } else if len(resp.Data) != 1 { 371 t.Fatal("only 1 status should be returned") 372 } 373 } 374 375 WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"}) 376 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 377 t.Fatal(resp.Error) 378 } else { 379 if resp.SeqReply != WebSocketClient.Sequence-1 { 380 t.Fatal("bad sequence number") 381 } 382 383 if len(resp.Data) != 2 { 384 t.Fatal("2 statuses should be returned") 385 } 386 } 387 388 WebSocketClient.GetStatusesByIds([]string{}) 389 if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil { 390 if resp.SeqReply != WebSocketClient.Sequence-1 { 391 t.Fatal("bad sequence number") 392 } 393 t.Fatal("should have errored - empty user ids") 394 } 395 396 WebSocketClient2.Close() 397 398 th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) 399 400 awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout 401 defer func() { 402 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout }) 403 }() 404 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 }) 405 406 time.Sleep(1500 * time.Millisecond) 407 408 th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) 409 th.App.SetStatusOnline(th.BasicUser.Id, false) 410 411 time.Sleep(1500 * time.Millisecond) 412 413 WebSocketClient.GetStatuses() 414 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 415 t.Fatal(resp.Error) 416 } else { 417 if resp.SeqReply != WebSocketClient.Sequence-1 { 418 t.Fatal("bad sequence number") 419 } 420 421 if _, ok := resp.Data[th.BasicUser2.Id]; ok { 422 t.Fatal("should not have had user status") 423 } 424 } 425 426 stop := make(chan bool) 427 onlineHit := false 428 awayHit := false 429 430 go func() { 431 for { 432 select { 433 case resp := <-WebSocketClient.EventChannel: 434 if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { 435 status := resp.Data["status"].(string) 436 if status == model.STATUS_ONLINE { 437 onlineHit = true 438 } else if status == model.STATUS_AWAY { 439 awayHit = true 440 } 441 } 442 case <-stop: 443 return 444 } 445 } 446 }() 447 448 time.Sleep(500 * time.Millisecond) 449 450 stop <- true 451 452 if !onlineHit { 453 t.Fatal("didn't get online event") 454 } 455 if !awayHit { 456 t.Fatal("didn't get away event") 457 } 458 459 time.Sleep(500 * time.Millisecond) 460 461 WebSocketClient.Close() 462 }