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 }