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