github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/api/websocket_test.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	//"encoding/json"
     9  	//"net/http"
    10  	"net/http"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/gorilla/websocket"
    15  	"github.com/mattermost/mattermost-server/model"
    16  )
    17  
    18  /*func TestWebSocketAuthentication(t *testing.T) {
    19  	th := Setup().InitBasic()
    20  	WebSocketClient, err := th.CreateWebSocketClient()
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	WebSocketClient.Listen()
    25  
    26  	time.Sleep(300 * time.Millisecond)
    27  	if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
    28  		t.Fatal("should have responded OK to authentication challenge")
    29  	}
    30  
    31  	WebSocketClient.SendMessage("ping", nil)
    32  	time.Sleep(300 * time.Millisecond)
    33  	if resp := <-WebSocketClient.ResponseChannel; resp.Data["text"].(string) != "pong" {
    34  		t.Fatal("wrong response")
    35  	}
    36  
    37  	WebSocketClient.Close()
    38  
    39  	authToken := WebSocketClient.AuthToken
    40  	WebSocketClient.AuthToken = "junk"
    41  	if err := WebSocketClient.Connect(); err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	WebSocketClient.Listen()
    45  
    46  	if resp := <-WebSocketClient.ResponseChannel; resp != nil {
    47  		t.Fatal("should have closed")
    48  	}
    49  
    50  	if conn, _, err := websocket.DefaultDialer.Dial(WebSocketClient.ApiUrl+"/users/websocket", nil); err != nil {
    51  		t.Fatal("should have connected")
    52  	} else {
    53  		req := &model.WebSocketRequest{}
    54  		req.Seq = 1
    55  		req.Action = "ping"
    56  		conn.WriteJSON(req)
    57  
    58  		closedAutomatically := false
    59  		hitNotAuthedError := false
    60  
    61  		go func() {
    62  			time.Sleep(10 * time.Second)
    63  			conn.Close()
    64  
    65  			if !closedAutomatically {
    66  				t.Fatal("should have closed automatically in 5 seconds")
    67  			}
    68  		}()
    69  
    70  		for {
    71  			if _, rawMsg, err := conn.ReadMessage(); err != nil {
    72  				closedAutomatically = true
    73  				conn.Close()
    74  				break
    75  			} else {
    76  				var response model.WebSocketResponse
    77  				if err := json.Unmarshal(rawMsg, &response); err != nil && !response.IsValid() {
    78  					t.Fatal("should not have failed")
    79  				} else {
    80  					if response.Error == nil || response.Error.Id != "api.web_socket_router.not_authenticated.app_error" {
    81  						t.Log(response.Error.Id)
    82  						t.Fatal("wrong error")
    83  						continue
    84  					}
    85  
    86  					hitNotAuthedError = true
    87  				}
    88  			}
    89  		}
    90  
    91  		if !hitNotAuthedError {
    92  			t.Fatal("should have received a not authenticated response")
    93  		}
    94  	}
    95  
    96  	header := http.Header{}
    97  	header.Set(model.HEADER_AUTH, "BEARER "+authToken)
    98  	if conn, _, err := websocket.DefaultDialer.Dial(WebSocketClient.ApiUrl+"/users/websocket", header); err != nil {
    99  		t.Fatal("should have connected")
   100  	} else {
   101  		if _, rawMsg, err := conn.ReadMessage(); err != nil {
   102  			t.Fatal("should not have closed automatically")
   103  		} else {
   104  			var event model.WebSocketEvent
   105  			if err := json.Unmarshal(rawMsg, &event); err != nil && !event.IsValid() {
   106  				t.Fatal("should not have failed")
   107  			} else if event.Event != model.WEBSOCKET_EVENT_HELLO {
   108  				t.Log(event.ToJson())
   109  				t.Fatal("should have helloed")
   110  			}
   111  		}
   112  
   113  		conn.Close()
   114  	}
   115  }*/
   116  
   117  func TestWebSocket(t *testing.T) {
   118  	th := Setup().InitBasic()
   119  	defer th.TearDown()
   120  
   121  	WebSocketClient, err := th.CreateWebSocketClient()
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  	defer WebSocketClient.Close()
   126  
   127  	time.Sleep(300 * time.Millisecond)
   128  
   129  	// Test closing and reconnecting
   130  	WebSocketClient.Close()
   131  	if err := WebSocketClient.Connect(); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	WebSocketClient.Listen()
   136  
   137  	time.Sleep(300 * time.Millisecond)
   138  	if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
   139  		t.Fatal("should have responded OK to authentication challenge")
   140  	}
   141  
   142  	WebSocketClient.SendMessage("ping", nil)
   143  	time.Sleep(300 * time.Millisecond)
   144  	if resp := <-WebSocketClient.ResponseChannel; resp.Data["text"].(string) != "pong" {
   145  		t.Fatal("wrong response")
   146  	}
   147  
   148  	WebSocketClient.SendMessage("", nil)
   149  	time.Sleep(300 * time.Millisecond)
   150  	if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.no_action.app_error" {
   151  		t.Fatal("should have been no action response")
   152  	}
   153  
   154  	WebSocketClient.SendMessage("junk", nil)
   155  	time.Sleep(300 * time.Millisecond)
   156  	if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_action.app_error" {
   157  		t.Fatal("should have been bad action response")
   158  	}
   159  
   160  	req := &model.WebSocketRequest{}
   161  	req.Seq = 0
   162  	req.Action = "ping"
   163  	WebSocketClient.Conn.WriteJSON(req)
   164  	time.Sleep(300 * time.Millisecond)
   165  	if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.web_socket_router.bad_seq.app_error" {
   166  		t.Fatal("should have been bad action response")
   167  	}
   168  
   169  	WebSocketClient.UserTyping("", "")
   170  	time.Sleep(300 * time.Millisecond)
   171  	if resp := <-WebSocketClient.ResponseChannel; resp.Error.Id != "api.websocket_handler.invalid_param.app_error" {
   172  		t.Fatal("should have been invalid param response")
   173  	} else {
   174  		if resp.Error.DetailedError != "" {
   175  			t.Fatal("detailed error not cleared")
   176  		}
   177  	}
   178  }
   179  
   180  func TestWebSocketEvent(t *testing.T) {
   181  	th := Setup().InitBasic()
   182  	defer th.TearDown()
   183  
   184  	WebSocketClient, err := th.CreateWebSocketClient()
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	defer WebSocketClient.Close()
   189  
   190  	WebSocketClient.Listen()
   191  
   192  	time.Sleep(300 * time.Millisecond)
   193  	if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
   194  		t.Fatal("should have responded OK to authentication challenge")
   195  	}
   196  
   197  	omitUser := make(map[string]bool, 1)
   198  	omitUser["somerandomid"] = true
   199  	evt1 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", th.BasicChannel.Id, "", omitUser)
   200  	evt1.Add("user_id", "somerandomid")
   201  	th.App.Publish(evt1)
   202  
   203  	time.Sleep(300 * time.Millisecond)
   204  
   205  	stop := make(chan bool)
   206  	eventHit := false
   207  
   208  	go func() {
   209  		for {
   210  			select {
   211  			case resp := <-WebSocketClient.EventChannel:
   212  				if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" {
   213  					eventHit = true
   214  				}
   215  			case <-stop:
   216  				return
   217  			}
   218  		}
   219  	}()
   220  
   221  	time.Sleep(400 * time.Millisecond)
   222  
   223  	stop <- true
   224  
   225  	if !eventHit {
   226  		t.Fatal("did not receive typing event")
   227  	}
   228  
   229  	evt2 := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_TYPING, "", "somerandomid", "", nil)
   230  	th.App.Publish(evt2)
   231  	time.Sleep(300 * time.Millisecond)
   232  
   233  	eventHit = false
   234  
   235  	go func() {
   236  		for {
   237  			select {
   238  			case resp := <-WebSocketClient.EventChannel:
   239  				if resp.Event == model.WEBSOCKET_EVENT_TYPING {
   240  					eventHit = true
   241  				}
   242  			case <-stop:
   243  				return
   244  			}
   245  		}
   246  	}()
   247  
   248  	time.Sleep(400 * time.Millisecond)
   249  
   250  	stop <- true
   251  
   252  	if eventHit {
   253  		t.Fatal("got typing event for bad channel id")
   254  	}
   255  }
   256  
   257  func TestCreateDirectChannelWithSocket(t *testing.T) {
   258  	th := Setup().InitBasic()
   259  	defer th.TearDown()
   260  
   261  	Client := th.BasicClient
   262  	user2 := th.BasicUser2
   263  
   264  	users := make([]*model.User, 0)
   265  	users = append(users, user2)
   266  
   267  	for i := 0; i < 10; i++ {
   268  		users = append(users, th.CreateUser(Client))
   269  	}
   270  
   271  	WebSocketClient, err := th.CreateWebSocketClient()
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  	defer WebSocketClient.Close()
   276  	WebSocketClient.Listen()
   277  
   278  	time.Sleep(300 * time.Millisecond)
   279  	if resp := <-WebSocketClient.ResponseChannel; resp.Status != model.STATUS_OK {
   280  		t.Fatal("should have responded OK to authentication challenge")
   281  	}
   282  
   283  	wsr := <-WebSocketClient.EventChannel
   284  	if wsr.Event != model.WEBSOCKET_EVENT_HELLO {
   285  		t.Fatal("missing hello")
   286  	}
   287  
   288  	stop := make(chan bool)
   289  	count := 0
   290  
   291  	go func() {
   292  		for {
   293  			select {
   294  			case wsr := <-WebSocketClient.EventChannel:
   295  				if wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED {
   296  					count = count + 1
   297  				}
   298  
   299  			case <-stop:
   300  				return
   301  			}
   302  		}
   303  	}()
   304  
   305  	for _, user := range users {
   306  		time.Sleep(100 * time.Millisecond)
   307  		if _, err := Client.CreateDirectChannel(user.Id); err != nil {
   308  			t.Fatal("failed to create DM channel")
   309  		}
   310  	}
   311  
   312  	time.Sleep(5000 * time.Millisecond)
   313  
   314  	stop <- true
   315  
   316  	if count != len(users) {
   317  		t.Fatal("We didn't get the proper amount of direct_added messages")
   318  	}
   319  
   320  }
   321  
   322  func TestWebsocketOriginSecurity(t *testing.T) {
   323  	th := Setup().InitBasic()
   324  	defer th.TearDown()
   325  
   326  	url := fmt.Sprintf("ws://localhost:%v", th.App.Srv.ListenAddr.Port)
   327  
   328  	// Should fail because origin doesn't match
   329  	_, _, err := websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   330  		"Origin": []string{"http://www.evil.com"},
   331  	})
   332  	if err == nil {
   333  		t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!")
   334  	}
   335  
   336  	// We are not a browser so we can spoof this just fine
   337  	_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   338  		"Origin": []string{fmt.Sprintf("http://localhost:%v", th.App.Srv.ListenAddr.Port)},
   339  	})
   340  	if err != nil {
   341  		t.Fatal(err)
   342  	}
   343  
   344  	// Should succeed now because open CORS
   345  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "*" })
   346  	_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   347  		"Origin": []string{"http://www.evil.com"},
   348  	})
   349  	if err != nil {
   350  		t.Fatal(err)
   351  	}
   352  
   353  	// Should succeed now because matching CORS
   354  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.evil.com" })
   355  	_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   356  		"Origin": []string{"http://www.evil.com"},
   357  	})
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	// Should fail because non-matching CORS
   363  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" })
   364  	_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   365  		"Origin": []string{"http://www.evil.com"},
   366  	})
   367  	if err == nil {
   368  		t.Fatal("Should have errored because Origin contain AllowCorsFrom")
   369  	}
   370  
   371  	// Should fail because non-matching CORS
   372  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "http://www.good.com" })
   373  	_, _, err = websocket.DefaultDialer.Dial(url+model.API_URL_SUFFIX_V3+"/users/websocket", http.Header{
   374  		"Origin": []string{"http://www.good.co"},
   375  	})
   376  	if err == nil {
   377  		t.Fatal("Should have errored because Origin does not match host! SECURITY ISSUE!")
   378  	}
   379  
   380  	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowCorsFrom = "" })
   381  }