github.com/jfrerich/mattermost-server@v5.8.0-rc2+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))
   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))
   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  }