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 }