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