github.com/mad-app/mattermost-server@v5.11.1+incompatible/api4/websocket_test.go (about) 1 // Copyright (c) 2017-present Mattermost, 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 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/mattermost/mattermost-server/store" 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 store.Must(th.App.Srv.Store.User().VerifyEmail(ruser.Id, ruser.Email)) 308 309 user2 := model.User{Email: strings.ToLower(model.NewId()) + "success+test@simulator.amazonses.com", Nickname: "Corey Hulen", Password: "passwd1"} 310 ruser2 := Client.Must(Client.CreateUser(&user2)).(*model.User) 311 th.LinkUserToTeam(ruser2, rteam) 312 store.Must(th.App.Srv.Store.User().VerifyEmail(ruser2.Id, ruser2.Email)) 313 314 Client.Login(user.Email, user.Password) 315 316 th.LoginBasic2() 317 318 WebSocketClient2, err2 := th.CreateWebSocketClient() 319 if err2 != nil { 320 t.Fatal(err2) 321 } 322 323 time.Sleep(1000 * time.Millisecond) 324 325 WebSocketClient.GetStatuses() 326 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 327 t.Fatal(resp.Error) 328 } else { 329 if resp.SeqReply != WebSocketClient.Sequence-1 { 330 t.Fatal("bad sequence number") 331 } 332 333 for _, status := range resp.Data { 334 if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE && status != model.STATUS_DND { 335 t.Fatalf("one of the statuses had an invalid value status=%v", status) 336 } 337 } 338 339 if status, ok := resp.Data[th.BasicUser2.Id]; !ok { 340 t.Log(resp.Data) 341 t.Fatal("should have had user status") 342 } else if status != model.STATUS_ONLINE { 343 t.Log(status) 344 t.Fatal("status should have been online") 345 } 346 } 347 348 WebSocketClient.GetStatusesByIds([]string{th.BasicUser2.Id}) 349 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 350 t.Fatal(resp.Error) 351 } else { 352 if resp.SeqReply != WebSocketClient.Sequence-1 { 353 t.Fatal("bad sequence number") 354 } 355 356 for _, status := range resp.Data { 357 if status != model.STATUS_OFFLINE && status != model.STATUS_AWAY && status != model.STATUS_ONLINE { 358 t.Fatal("one of the statuses had an invalid value") 359 } 360 } 361 362 if status, ok := resp.Data[th.BasicUser2.Id]; !ok { 363 t.Log(len(resp.Data)) 364 t.Fatal("should have had user status") 365 } else if status != model.STATUS_ONLINE { 366 t.Log(status) 367 t.Fatal("status should have been online") 368 } else if len(resp.Data) != 1 { 369 t.Fatal("only 1 status should be returned") 370 } 371 } 372 373 WebSocketClient.GetStatusesByIds([]string{ruser2.Id, "junk"}) 374 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 375 t.Fatal(resp.Error) 376 } else { 377 if resp.SeqReply != WebSocketClient.Sequence-1 { 378 t.Fatal("bad sequence number") 379 } 380 381 if len(resp.Data) != 2 { 382 t.Fatal("2 statuses should be returned") 383 } 384 } 385 386 WebSocketClient.GetStatusesByIds([]string{}) 387 if resp := <-WebSocketClient.ResponseChannel; resp.Error == nil { 388 if resp.SeqReply != WebSocketClient.Sequence-1 { 389 t.Fatal("bad sequence number") 390 } 391 t.Fatal("should have errored - empty user ids") 392 } 393 394 WebSocketClient2.Close() 395 396 th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) 397 398 awayTimeout := *th.App.Config().TeamSettings.UserStatusAwayTimeout 399 defer func() { 400 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = awayTimeout }) 401 }() 402 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.UserStatusAwayTimeout = 1 }) 403 404 time.Sleep(1500 * time.Millisecond) 405 406 th.App.SetStatusAwayIfNeeded(th.BasicUser.Id, false) 407 th.App.SetStatusOnline(th.BasicUser.Id, false) 408 409 time.Sleep(1500 * time.Millisecond) 410 411 WebSocketClient.GetStatuses() 412 if resp := <-WebSocketClient.ResponseChannel; resp.Error != nil { 413 t.Fatal(resp.Error) 414 } else { 415 if resp.SeqReply != WebSocketClient.Sequence-1 { 416 t.Fatal("bad sequence number") 417 } 418 419 if _, ok := resp.Data[th.BasicUser2.Id]; ok { 420 t.Fatal("should not have had user status") 421 } 422 } 423 424 stop := make(chan bool) 425 onlineHit := false 426 awayHit := false 427 428 go func() { 429 for { 430 select { 431 case resp := <-WebSocketClient.EventChannel: 432 if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { 433 status := resp.Data["status"].(string) 434 if status == model.STATUS_ONLINE { 435 onlineHit = true 436 } else if status == model.STATUS_AWAY { 437 awayHit = true 438 } 439 } 440 case <-stop: 441 return 442 } 443 } 444 }() 445 446 time.Sleep(500 * time.Millisecond) 447 448 stop <- true 449 450 if !onlineHit { 451 t.Fatal("didn't get online event") 452 } 453 if !awayHit { 454 t.Fatal("didn't get away event") 455 } 456 457 time.Sleep(500 * time.Millisecond) 458 459 WebSocketClient.Close() 460 }