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