From 2114e96f0045376f41d5c318f7cf45b6b50141dc Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 27 Jun 2018 17:19:06 -0400 Subject: Reset status to away after web conn disconnect if necessary (#8988) --- app/session.go | 3 +++ app/status.go | 16 ++++++++++++++++ app/web_conn.go | 18 ++++++++++-------- app/web_hub.go | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) (limited to 'app') diff --git a/app/session.go b/app/session.go index 53170cec0..5289aefaa 100644 --- a/app/session.go +++ b/app/session.go @@ -227,6 +227,9 @@ func (a *App) AttachDeviceId(sessionId string, deviceId string, expiresAt int64) func (a *App) UpdateLastActivityAtIfNeeded(session model.Session) { now := model.GetMillis() + + a.UpdateWebConnUserActivity(session, now) + if now-session.LastActivityAt < model.SESSION_ACTIVITY_TIMEOUT { return } diff --git a/app/status.go b/app/status.go index e2367a396..460cbbbd0 100644 --- a/app/status.go +++ b/app/status.go @@ -161,6 +161,22 @@ func (a *App) GetUserStatusesByIds(userIds []string) ([]*model.Status, *model.Ap return statusMap, nil } +// SetStatusLastActivityAt sets the last activity at for a user on the local app server and updates +// status to away if needed. Used by the WS to set status to away if an 'online' device disconnects +// while an 'away' device is still connected +func (a *App) SetStatusLastActivityAt(userId string, activityAt int64) { + var status *model.Status + var err *model.AppError + if status, err = a.GetStatus(userId); err != nil { + return + } + + status.LastActivityAt = activityAt + + a.AddStatusCacheSkipClusterSend(status) + a.SetStatusAwayIfNeeded(userId, false) +} + func (a *App) SetStatusOnline(userId string, sessionId string, manual bool) { if !*a.Config().ServiceSettings.EnableUserStatuses { return diff --git a/app/web_conn.go b/app/web_conn.go index 9d8134f34..dd01a8e31 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -33,6 +33,7 @@ type WebConn struct { Send chan model.WebSocketMessage sessionToken atomic.Value session atomic.Value + LastUserActivityAt int64 UserId string T goi18n.TranslateFunc Locale string @@ -52,14 +53,15 @@ func (a *App) NewWebConn(ws *websocket.Conn, session model.Session, t goi18n.Tra } wc := &WebConn{ - App: a, - Send: make(chan model.WebSocketMessage, SEND_QUEUE_SIZE), - WebSocket: ws, - UserId: session.UserId, - T: t, - Locale: locale, - endWritePump: make(chan struct{}, 2), - pumpFinished: make(chan struct{}, 1), + App: a, + Send: make(chan model.WebSocketMessage, SEND_QUEUE_SIZE), + WebSocket: ws, + LastUserActivityAt: model.GetMillis(), + UserId: session.UserId, + T: t, + Locale: locale, + endWritePump: make(chan struct{}, 2), + pumpFinished: make(chan struct{}, 1), } wc.SetSession(&session) diff --git a/app/web_hub.go b/app/web_hub.go index 2ce78b5ef..5bb86ee38 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -23,6 +23,12 @@ const ( DEADLOCK_WARN = (BROADCAST_QUEUE_SIZE * 99) / 100 // number of buffered messages before printing stack trace ) +type WebConnActivityMessage struct { + UserId string + SessionToken string + ActivityAt int64 +} + type Hub struct { // connectionCount should be kept first. // See https://github.com/mattermost/mattermost-server/pull/7281 @@ -35,6 +41,7 @@ type Hub struct { stop chan struct{} didStop chan struct{} invalidateUser chan string + activity chan *WebConnActivityMessage ExplicitStop bool goroutineId int } @@ -48,6 +55,7 @@ func (a *App) NewWebHub() *Hub { stop: make(chan struct{}), didStop: make(chan struct{}), invalidateUser: make(chan string), + activity: make(chan *WebConnActivityMessage), ExplicitStop: false, } } @@ -330,6 +338,13 @@ func (a *App) InvalidateWebConnSessionCacheForUser(userId string) { } } +func (a *App) UpdateWebConnUserActivity(session model.Session, activityAt int64) { + hub := a.GetHubForUserId(session.UserId) + if hub != nil { + hub.UpdateActivity(session.UserId, session.Token, activityAt) + } +} + func (h *Hub) Register(webConn *WebConn) { h.register <- webConn @@ -355,6 +370,10 @@ func (h *Hub) InvalidateUser(userId string) { h.invalidateUser <- userId } +func (h *Hub) UpdateActivity(userId, sessionToken string, activityAt int64) { + h.activity <- &WebConnActivityMessage{UserId: userId, SessionToken: sessionToken, ActivityAt: activityAt} +} + func getGoroutineId() int { var buf [64]byte n := runtime.Stack(buf[:], false) @@ -395,15 +414,34 @@ func (h *Hub) Start() { continue } - if len(connections.ForUser(webCon.UserId)) == 0 { + conns := connections.ForUser(webCon.UserId) + if len(conns) == 0 { h.app.Go(func() { h.app.SetStatusOffline(webCon.UserId, false) }) + } else { + var latestActivity int64 = 0 + for _, conn := range conns { + if conn.LastUserActivityAt > latestActivity { + latestActivity = conn.LastUserActivityAt + } + } + if h.app.IsUserAway(latestActivity) { + h.app.Go(func() { + h.app.SetStatusLastActivityAt(webCon.UserId, latestActivity) + }) + } } case userId := <-h.invalidateUser: for _, webCon := range connections.ForUser(userId) { webCon.InvalidateCache() } + case activity := <-h.activity: + for _, webCon := range connections.ForUser(activity.UserId) { + if webCon.GetSessionToken() == activity.SessionToken { + webCon.LastUserActivityAt = activity.ActivityAt + } + } case msg := <-h.broadcast: candidates := connections.All() if msg.Broadcast.UserId != "" { -- cgit v1.2.3-1-g7c22 From 66dc9a5206b318a92f5cd9b41d53abc461c4deb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Espino?= Date: Thu, 28 Jun 2018 13:42:38 +0200 Subject: Add english missed texts to i18n/en.json (#8987) * Add English missed texts to i18n/en.json * Update en.json --- app/import.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/import.go b/app/import.go index 64e53fe93..df20b2ba7 100644 --- a/app/import.go +++ b/app/import.go @@ -1200,7 +1200,7 @@ func validateUserImportData(data *UserImportData) *model.AppError { } if data.Password != nil && len(*data.Password) == 0 { - return model.NewAppError("BulkImport", "app.import.validate_user_import_data.pasword_length.error", nil, "", http.StatusBadRequest) + return model.NewAppError("BulkImport", "app.import.validate_user_import_data.password_length.error", nil, "", http.StatusBadRequest) } if data.Password != nil && len(*data.Password) > model.USER_PASSWORD_MAX_LENGTH { -- cgit v1.2.3-1-g7c22 From 4b2a6263547363c43dba9c320c077ad295caaf4d Mon Sep 17 00:00:00 2001 From: Jerry Kurian Date: Thu, 28 Jun 2018 09:45:20 -0400 Subject: add MaxLifetimeconns to server (#9004) Update e.json string names and remove console log reorder variables update db.setConnMaxLifetime to take in *settings.MaxLifetimeConns Add MaxLifetimeConns to test Update variable names like MaxLifetimeConns to ConnMaxLifetimeMilliseconds Update time passed to SetConnMaxLifetime Revert "Update time passed to SetConnMaxLifetime" This reverts commit dc1c1b6d5a6404d4ee11c65d4756c19f9316794f. update en.json Update supplier_test.go and docker.go ConnMaxLifetimeMilliseconds to 3600000 --- app/diagnostics.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'app') diff --git a/app/diagnostics.go b/app/diagnostics.go index 3df62c41d..e9b3405be 100644 --- a/app/diagnostics.go +++ b/app/diagnostics.go @@ -297,13 +297,14 @@ func (a *App) trackConfig() { }) a.SendDiagnostic(TRACK_CONFIG_SQL, map[string]interface{}{ - "driver_name": *cfg.SqlSettings.DriverName, - "trace": cfg.SqlSettings.Trace, - "max_idle_conns": *cfg.SqlSettings.MaxIdleConns, - "max_open_conns": *cfg.SqlSettings.MaxOpenConns, - "data_source_replicas": len(cfg.SqlSettings.DataSourceReplicas), - "data_source_search_replicas": len(cfg.SqlSettings.DataSourceSearchReplicas), - "query_timeout": *cfg.SqlSettings.QueryTimeout, + "driver_name": *cfg.SqlSettings.DriverName, + "trace": cfg.SqlSettings.Trace, + "max_idle_conns": *cfg.SqlSettings.MaxIdleConns, + "conn_max_lifetime_milliseconds": *cfg.SqlSettings.ConnMaxLifetimeMilliseconds, + "max_open_conns": *cfg.SqlSettings.MaxOpenConns, + "data_source_replicas": len(cfg.SqlSettings.DataSourceReplicas), + "data_source_search_replicas": len(cfg.SqlSettings.DataSourceSearchReplicas), + "query_timeout": *cfg.SqlSettings.QueryTimeout, }) a.SendDiagnostic(TRACK_CONFIG_LOG, map[string]interface{}{ -- cgit v1.2.3-1-g7c22 From c371ae4db142d061d7e22d9cb72f3942e4d45faf Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Fri, 29 Jun 2018 16:50:06 +0100 Subject: Make import compatible with scheme-roles. (#9028) This doesn't introduce new fields as we would then break backwards compatability with old "roles" fields. We can add custom fields if/when we go to "version 2" of the bulk import format. --- app/import.go | 48 ++++++++++++++++++--- app/import_test.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/import.go b/app/import.go index df20b2ba7..baf936567 100644 --- a/app/import.go +++ b/app/import.go @@ -1062,10 +1062,24 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod } var roles string + isSchemeUser := true + isSchemeAdmin := false + if tdata.Roles == nil { - roles = model.TEAM_USER_ROLE_ID + isSchemeUser = true } else { - roles = *tdata.Roles + rawRoles := *tdata.Roles + explicitRoles := []string{} + for _, role := range strings.Fields(rawRoles) { + if role == model.TEAM_USER_ROLE_ID { + isSchemeUser = true + } else if role == model.TEAM_ADMIN_ROLE_ID { + isSchemeAdmin = true + } else { + explicitRoles = append(explicitRoles, role) + } + } + roles = strings.Join(explicitRoles, " ") } var member *model.TeamMember @@ -1073,12 +1087,16 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod return err } - if member.Roles != roles { + if member.ExplicitRoles != roles { if _, err := a.UpdateTeamMemberRoles(team.Id, user.Id, roles); err != nil { return err } } + if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser { + a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeUser, isSchemeAdmin) + } + if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id); err != nil { return err } else if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil { @@ -1108,10 +1126,24 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember } var roles string + isSchemeUser := true + isSchemeAdmin := false + if cdata.Roles == nil { - roles = model.CHANNEL_USER_ROLE_ID + isSchemeUser = true } else { - roles = *cdata.Roles + rawRoles := *cdata.Roles + explicitRoles := []string{} + for _, role := range strings.Fields(rawRoles) { + if role == model.CHANNEL_USER_ROLE_ID { + isSchemeUser = true + } else if role == model.CHANNEL_ADMIN_ROLE_ID { + isSchemeAdmin = true + } else { + explicitRoles = append(explicitRoles, role) + } + } + roles = strings.Join(explicitRoles, " ") } var member *model.ChannelMember @@ -1123,12 +1155,16 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember } } - if member.Roles != roles { + if member.ExplicitRoles != roles { if _, err := a.UpdateChannelMemberRoles(channel.Id, user.Id, roles); err != nil { return err } } + if member.SchemeAdmin != isSchemeAdmin || member.SchemeUser != isSchemeUser { + a.UpdateChannelMemberSchemeRoles(channel.Id, user.Id, isSchemeUser, isSchemeAdmin) + } + if cdata.NotifyProps != nil { notifyProps := member.NotifyProps diff --git a/app/import_test.go b/app/import_test.go index b27290289..e7bc055a4 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -2598,6 +2598,126 @@ func TestImportImportUser(t *testing.T) { checkNotifyProp(t, user, model.CHANNEL_MENTIONS_NOTIFY_PROP, "false") checkNotifyProp(t, user, model.COMMENTS_NOTIFY_PROP, model.COMMENTS_NOTIFY_ANY) checkNotifyProp(t, user, model.MENTION_KEYS_NOTIFY_PROP, "misc") + + // Test importing a user with roles set to a team and a channel which are affected by an override scheme. + // The import subsystem should translate `channel_admin/channel_user/team_admin/team_user` + // to the appropriate scheme-managed-role booleans. + + // Mark the phase 2 permissions migration as completed. + <-th.App.Srv.Store.System().Save(&model.System{Name: model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2, Value: "true"}) + + defer func() { + <-th.App.Srv.Store.System().PermanentDeleteByName(model.MIGRATION_KEY_ADVANCED_PERMISSIONS_PHASE_2) + }() + + teamSchemeData := &SchemeImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + Scope: ptrStr("team"), + DefaultTeamUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultTeamAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelUserRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + DefaultChannelAdminRole: &RoleImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr(model.NewId()), + }, + Description: ptrStr("description"), + } + + if err := th.App.ImportScheme(teamSchemeData, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + var teamScheme *model.Scheme + if res := <-th.App.Srv.Store.Scheme().GetByName(*teamSchemeData.Name); res.Err != nil { + t.Fatalf("Failed to import scheme: %v", res.Err) + } else { + teamScheme = res.Data.(*model.Scheme) + } + + teamData := &TeamImportData{ + Name: ptrStr(model.NewId()), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Description: ptrStr("The team description."), + AllowOpenInvite: ptrBool(true), + Scheme: &teamScheme.Name, + } + if err := th.App.ImportTeam(teamData, false); err != nil { + t.Fatalf("Import should have succeeded: %v", err.Error()) + } + team, err = th.App.GetTeamByName(teamName) + if err != nil { + t.Fatalf("Failed to get team from database.") + } + + channelData := &ChannelImportData{ + Team: &teamName, + Name: ptrStr(model.NewId()), + DisplayName: ptrStr("Display Name"), + Type: ptrStr("O"), + Header: ptrStr("Channe Header"), + Purpose: ptrStr("Channel Purpose"), + } + if err := th.App.ImportChannel(channelData, false); err != nil { + t.Fatalf("Import should have succeeded.") + } + channel, err = th.App.GetChannelByName(*channelData.Name, team.Id) + if err != nil { + t.Fatalf("Failed to get channel from database: %v", err.Error()) + } + + // Test with a valid team & valid channel name in apply mode. + userData := &UserImportData{ + Username: &username, + Email: ptrStr(model.NewId() + "@example.com"), + Teams: &[]UserTeamImportData{ + { + Name: &team.Name, + Roles: ptrStr("team_user team_admin"), + Channels: &[]UserChannelImportData{ + { + Name: &channel.Name, + Roles: ptrStr("channel_admin channel_user"), + }, + }, + }, + }, + } + if err := th.App.ImportUser(userData, false); err != nil { + t.Fatalf("Should have succeeded.") + } + + user, err = th.App.GetUserByUsername(*userData.Username) + if err != nil { + t.Fatalf("Failed to get user from database.") + } + + teamMember, err := th.App.GetTeamMember(team.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the team member") + } + assert.True(t, teamMember.SchemeAdmin) + assert.True(t, teamMember.SchemeUser) + assert.Equal(t, "", teamMember.ExplicitRoles) + + channelMember, err := th.App.GetChannelMember(channel.Id, user.Id) + if err != nil { + t.Fatalf("Failed to get the channel member") + } + assert.True(t, channelMember.SchemeAdmin) + assert.True(t, channelMember.SchemeUser) + assert.Equal(t, "", channelMember.ExplicitRoles) + } func AssertAllPostsCount(t *testing.T, a *App, initialCount int64, change int64, teamName string) { -- cgit v1.2.3-1-g7c22 From 4245797cb23b3f9dc99ff556a5ee22c4e14140bc Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Fri, 29 Jun 2018 17:17:35 -0400 Subject: redirect, vs. proxy, 80->443 without LE enabled (#9020) The code incorrectly got refactored to proxy instead of forward, deviating from the behaviour when LE is enabled. --- app/server.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/server.go b/app/server.go index d71a884d2..769690295 100644 --- a/app/server.go +++ b/app/server.go @@ -92,15 +92,23 @@ func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second -func redirectHTTPToHTTPS(w http.ResponseWriter, r *http.Request) { - if r.Host == "" { - http.Error(w, "Not Found", http.StatusNotFound) +// golang.org/x/crypto/acme/autocert/autocert.go +func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(w, "Use HTTPS", http.StatusBadRequest) + return } + target := "https://" + stripPort(r.Host) + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusFound) +} - url := r.URL - url.Host = r.Host - url.Scheme = "https" - http.Redirect(w, r, url.String(), http.StatusFound) +// golang.org/x/crypto/acme/autocert/autocert.go +func stripPort(hostport string) string { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return hostport + } + return net.JoinHostPort(host, "443") } func (a *App) StartServer() error { @@ -182,7 +190,7 @@ func (a *App) StartServer() error { defer redirectListener.Close() server := &http.Server{ - Handler: handler, + Handler: http.HandlerFunc(handleHTTPRedirect), ErrorLog: a.Log.StdLog(mlog.String("source", "forwarder_server")), } server.Serve(redirectListener) -- cgit v1.2.3-1-g7c22