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

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package app
     5  
     6  import (
     7  	"fmt"
     8  	"sync/atomic"
     9  	"time"
    10  
    11  	"github.com/mattermost/mattermost-server/mlog"
    12  	"github.com/mattermost/mattermost-server/model"
    13  
    14  	"github.com/gorilla/websocket"
    15  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    16  )
    17  
    18  const (
    19  	SEND_QUEUE_SIZE           = 256
    20  	SEND_SLOW_WARN            = (SEND_QUEUE_SIZE * 50) / 100
    21  	SEND_DEADLOCK_WARN        = (SEND_QUEUE_SIZE * 95) / 100
    22  	WRITE_WAIT                = 30 * time.Second
    23  	PONG_WAIT                 = 100 * time.Second
    24  	PING_PERIOD               = (PONG_WAIT * 6) / 10
    25  	AUTH_TIMEOUT              = 5 * time.Second
    26  	WEBCONN_MEMBER_CACHE_TIME = 1000 * 60 * 30 // 30 minutes
    27  )
    28  
    29  type WebConn struct {
    30  	sessionExpiresAt          int64 // This should stay at the top for 64-bit alignment of 64-bit words accessed atomically
    31  	App                       *App
    32  	WebSocket                 *websocket.Conn
    33  	Send                      chan model.WebSocketMessage
    34  	sessionToken              atomic.Value
    35  	session                   atomic.Value
    36  	UserId                    string
    37  	T                         goi18n.TranslateFunc
    38  	Locale                    string
    39  	AllChannelMembers         map[string]string
    40  	LastAllChannelMembersTime int64
    41  	Sequence                  int64
    42  	endWritePump              chan struct{}
    43  	pumpFinished              chan struct{}
    44  }
    45  
    46  func (a *App) NewWebConn(ws *websocket.Conn, session model.Session, t goi18n.TranslateFunc, locale string) *WebConn {
    47  	if len(session.UserId) > 0 {
    48  		a.Go(func() {
    49  			a.SetStatusOnline(session.UserId, session.Id, false)
    50  			a.UpdateLastActivityAtIfNeeded(session)
    51  		})
    52  	}
    53  
    54  	wc := &WebConn{
    55  		App:          a,
    56  		Send:         make(chan model.WebSocketMessage, SEND_QUEUE_SIZE),
    57  		WebSocket:    ws,
    58  		UserId:       session.UserId,
    59  		T:            t,
    60  		Locale:       locale,
    61  		endWritePump: make(chan struct{}, 2),
    62  		pumpFinished: make(chan struct{}, 1),
    63  	}
    64  
    65  	wc.SetSession(&session)
    66  	wc.SetSessionToken(session.Token)
    67  	wc.SetSessionExpiresAt(session.ExpiresAt)
    68  
    69  	return wc
    70  }
    71  
    72  func (wc *WebConn) Close() {
    73  	wc.WebSocket.Close()
    74  	wc.endWritePump <- struct{}{}
    75  	<-wc.pumpFinished
    76  }
    77  
    78  func (c *WebConn) GetSessionExpiresAt() int64 {
    79  	return atomic.LoadInt64(&c.sessionExpiresAt)
    80  }
    81  
    82  func (c *WebConn) SetSessionExpiresAt(v int64) {
    83  	atomic.StoreInt64(&c.sessionExpiresAt, v)
    84  }
    85  
    86  func (c *WebConn) GetSessionToken() string {
    87  	return c.sessionToken.Load().(string)
    88  }
    89  
    90  func (c *WebConn) SetSessionToken(v string) {
    91  	c.sessionToken.Store(v)
    92  }
    93  
    94  func (c *WebConn) GetSession() *model.Session {
    95  	return c.session.Load().(*model.Session)
    96  }
    97  
    98  func (c *WebConn) SetSession(v *model.Session) {
    99  	if v != nil {
   100  		v = v.DeepCopy()
   101  	}
   102  
   103  	c.session.Store(v)
   104  }
   105  
   106  func (c *WebConn) Pump() {
   107  	ch := make(chan struct{}, 1)
   108  	go func() {
   109  		c.writePump()
   110  		ch <- struct{}{}
   111  	}()
   112  	c.readPump()
   113  	c.endWritePump <- struct{}{}
   114  	<-ch
   115  	c.App.HubUnregister(c)
   116  	c.pumpFinished <- struct{}{}
   117  }
   118  
   119  func (c *WebConn) readPump() {
   120  	defer func() {
   121  		c.WebSocket.Close()
   122  	}()
   123  	c.WebSocket.SetReadLimit(model.SOCKET_MAX_MESSAGE_SIZE_KB)
   124  	c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
   125  	c.WebSocket.SetPongHandler(func(string) error {
   126  		c.WebSocket.SetReadDeadline(time.Now().Add(PONG_WAIT))
   127  		if c.IsAuthenticated() {
   128  			c.App.Go(func() {
   129  				c.App.SetStatusAwayIfNeeded(c.UserId, false)
   130  			})
   131  		}
   132  		return nil
   133  	})
   134  
   135  	for {
   136  		var req model.WebSocketRequest
   137  		if err := c.WebSocket.ReadJSON(&req); err != nil {
   138  			// browsers will appear as CloseNoStatusReceived
   139  			if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   140  				mlog.Debug(fmt.Sprintf("websocket.read: client side closed socket userId=%v", c.UserId))
   141  			} else {
   142  				mlog.Debug(fmt.Sprintf("websocket.read: closing websocket for userId=%v error=%v", c.UserId, err.Error()))
   143  			}
   144  
   145  			return
   146  		} else {
   147  			c.App.Srv.WebSocketRouter.ServeWebSocket(c, &req)
   148  		}
   149  	}
   150  }
   151  
   152  func (c *WebConn) writePump() {
   153  	ticker := time.NewTicker(PING_PERIOD)
   154  	authTicker := time.NewTicker(AUTH_TIMEOUT)
   155  
   156  	defer func() {
   157  		ticker.Stop()
   158  		authTicker.Stop()
   159  		c.WebSocket.Close()
   160  	}()
   161  
   162  	for {
   163  		select {
   164  		case msg, ok := <-c.Send:
   165  			if !ok {
   166  				c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   167  				c.WebSocket.WriteMessage(websocket.CloseMessage, []byte{})
   168  				return
   169  			}
   170  
   171  			evt, evtOk := msg.(*model.WebSocketEvent)
   172  
   173  			skipSend := false
   174  			if len(c.Send) >= SEND_SLOW_WARN {
   175  				// When the pump starts to get slow we'll drop non-critical messages
   176  				if msg.EventType() == model.WEBSOCKET_EVENT_TYPING ||
   177  					msg.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE ||
   178  					msg.EventType() == model.WEBSOCKET_EVENT_CHANNEL_VIEWED {
   179  					mlog.Info(fmt.Sprintf("websocket.slow: dropping message userId=%v type=%v channelId=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId))
   180  					skipSend = true
   181  				}
   182  			}
   183  
   184  			if !skipSend {
   185  				var msgBytes []byte
   186  				if evtOk {
   187  					cpyEvt := &model.WebSocketEvent{}
   188  					*cpyEvt = *evt
   189  					cpyEvt.Sequence = c.Sequence
   190  					msgBytes = []byte(cpyEvt.ToJson())
   191  					c.Sequence++
   192  				} else {
   193  					msgBytes = []byte(msg.ToJson())
   194  				}
   195  
   196  				if len(c.Send) >= SEND_DEADLOCK_WARN {
   197  					if evtOk {
   198  						mlog.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v channelId=%v size=%v", c.UserId, msg.EventType(), evt.Broadcast.ChannelId, len(msg.ToJson())))
   199  					} else {
   200  						mlog.Error(fmt.Sprintf("websocket.full: message userId=%v type=%v size=%v", c.UserId, msg.EventType(), len(msg.ToJson())))
   201  					}
   202  				}
   203  
   204  				c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   205  				if err := c.WebSocket.WriteMessage(websocket.TextMessage, msgBytes); err != nil {
   206  					// browsers will appear as CloseNoStatusReceived
   207  					if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   208  						mlog.Debug(fmt.Sprintf("websocket.send: client side closed socket userId=%v", c.UserId))
   209  					} else {
   210  						mlog.Debug(fmt.Sprintf("websocket.send: closing websocket for userId=%v, error=%v", c.UserId, err.Error()))
   211  					}
   212  
   213  					return
   214  				}
   215  
   216  				if c.App.Metrics != nil {
   217  					c.App.Go(func() {
   218  						c.App.Metrics.IncrementWebSocketBroadcast(msg.EventType())
   219  					})
   220  				}
   221  
   222  			}
   223  		case <-ticker.C:
   224  			c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
   225  			if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
   226  				// browsers will appear as CloseNoStatusReceived
   227  				if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) {
   228  					mlog.Debug(fmt.Sprintf("websocket.ticker: client side closed socket userId=%v", c.UserId))
   229  				} else {
   230  					mlog.Debug(fmt.Sprintf("websocket.ticker: closing websocket for userId=%v error=%v", c.UserId, err.Error()))
   231  				}
   232  
   233  				return
   234  			}
   235  		case <-c.endWritePump:
   236  			return
   237  		case <-authTicker.C:
   238  			if c.GetSessionToken() == "" {
   239  				mlog.Debug(fmt.Sprintf("websocket.authTicker: did not authenticate ip=%v", c.WebSocket.RemoteAddr()))
   240  				return
   241  			}
   242  			authTicker.Stop()
   243  		}
   244  	}
   245  }
   246  
   247  func (webCon *WebConn) InvalidateCache() {
   248  	webCon.AllChannelMembers = nil
   249  	webCon.LastAllChannelMembersTime = 0
   250  	webCon.SetSession(nil)
   251  	webCon.SetSessionExpiresAt(0)
   252  }
   253  
   254  func (webCon *WebConn) IsAuthenticated() bool {
   255  	// Check the expiry to see if we need to check for a new session
   256  	if webCon.GetSessionExpiresAt() < model.GetMillis() {
   257  		if webCon.GetSessionToken() == "" {
   258  			return false
   259  		}
   260  
   261  		session, err := webCon.App.GetSession(webCon.GetSessionToken())
   262  		if err != nil {
   263  			mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error()))
   264  			webCon.SetSessionToken("")
   265  			webCon.SetSession(nil)
   266  			webCon.SetSessionExpiresAt(0)
   267  			return false
   268  		}
   269  
   270  		webCon.SetSession(session)
   271  		webCon.SetSessionExpiresAt(session.ExpiresAt)
   272  	}
   273  
   274  	return true
   275  }
   276  
   277  func (webCon *WebConn) SendHello() {
   278  	msg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_HELLO, "", "", webCon.UserId, nil)
   279  	msg.Add("server_version", fmt.Sprintf("%v.%v.%v.%v", model.CurrentVersion, model.BuildNumber, webCon.App.ClientConfigHash(), webCon.App.License() != nil))
   280  	webCon.Send <- msg
   281  }
   282  
   283  func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
   284  	// IMPORTANT: Do not send event if WebConn does not have a session
   285  	if !webCon.IsAuthenticated() {
   286  		return false
   287  	}
   288  
   289  	// If the event contains sanitized data, only send to users that don't have permission to
   290  	// see sensitive data. Prevents admin clients from receiving events with bad data
   291  	var hasReadPrivateDataPermission *bool
   292  	if msg.Broadcast.ContainsSanitizedData {
   293  		hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
   294  
   295  		if *hasReadPrivateDataPermission {
   296  			return false
   297  		}
   298  	}
   299  
   300  	// If the event contains sensitive data, only send to users with permission to see it
   301  	if msg.Broadcast.ContainsSensitiveData {
   302  		if hasReadPrivateDataPermission == nil {
   303  			hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id))
   304  		}
   305  
   306  		if !*hasReadPrivateDataPermission {
   307  			return false
   308  		}
   309  	}
   310  
   311  	// If the event is destined to a specific user
   312  	if len(msg.Broadcast.UserId) > 0 {
   313  		if webCon.UserId == msg.Broadcast.UserId {
   314  			return true
   315  		} else {
   316  			return false
   317  		}
   318  	}
   319  
   320  	// if the user is omitted don't send the message
   321  	if len(msg.Broadcast.OmitUsers) > 0 {
   322  		if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok {
   323  			return false
   324  		}
   325  	}
   326  
   327  	// Only report events to users who are in the channel for the event
   328  	if len(msg.Broadcast.ChannelId) > 0 {
   329  		if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME {
   330  			webCon.AllChannelMembers = nil
   331  			webCon.LastAllChannelMembersTime = 0
   332  		}
   333  
   334  		if webCon.AllChannelMembers == nil {
   335  			if result := <-webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil {
   336  				mlog.Error("webhub.shouldSendEvent: " + result.Err.Error())
   337  				return false
   338  			} else {
   339  				webCon.AllChannelMembers = result.Data.(map[string]string)
   340  				webCon.LastAllChannelMembersTime = model.GetMillis()
   341  			}
   342  		}
   343  
   344  		if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok {
   345  			return true
   346  		} else {
   347  			return false
   348  		}
   349  	}
   350  
   351  	// Only report events to users who are in the team for the event
   352  	if len(msg.Broadcast.TeamId) > 0 {
   353  		return webCon.IsMemberOfTeam(msg.Broadcast.TeamId)
   354  
   355  	}
   356  
   357  	return true
   358  }
   359  
   360  func (webCon *WebConn) IsMemberOfTeam(teamId string) bool {
   361  
   362  	currentSession := webCon.GetSession()
   363  
   364  	if currentSession == nil || len(currentSession.Token) == 0 {
   365  		session, err := webCon.App.GetSession(webCon.GetSessionToken())
   366  		if err != nil {
   367  			mlog.Error(fmt.Sprintf("Invalid session err=%v", err.Error()))
   368  			return false
   369  		} else {
   370  			webCon.SetSession(session)
   371  			currentSession = session
   372  		}
   373  	}
   374  
   375  	member := currentSession.GetTeamByTeamId(teamId)
   376  
   377  	if member != nil {
   378  		return true
   379  	} else {
   380  		return false
   381  	}
   382  }