diff options
author | JoramWilander <jwawilander@gmail.com> | 2018-06-30 12:24:04 -0400 |
---|---|---|
committer | JoramWilander <jwawilander@gmail.com> | 2018-06-30 12:24:04 -0400 |
commit | 3848cb7e79e019e2f0878d6e2377ad36b3c7ca43 (patch) | |
tree | 98beeb962ffc6d7c3f8cd38651eaccab4a9685fa /app | |
parent | 88c5e469ca869d9e8ceadb0f2b03e86005102f24 (diff) | |
parent | 2362a5b7f7c2b4bf2b35b104e8616d847b33b0be (diff) | |
download | chat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.tar.gz chat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.tar.bz2 chat-3848cb7e79e019e2f0878d6e2377ad36b3c7ca43.zip |
Merge branch 'master' into plugins-2
Diffstat (limited to 'app')
-rw-r--r-- | app/diagnostics.go | 15 | ||||
-rw-r--r-- | app/import.go | 50 | ||||
-rw-r--r-- | app/import_test.go | 120 | ||||
-rw-r--r-- | app/server.go | 24 | ||||
-rw-r--r-- | app/session.go | 3 | ||||
-rw-r--r-- | app/status.go | 16 | ||||
-rw-r--r-- | app/web_conn.go | 18 | ||||
-rw-r--r-- | app/web_hub.go | 40 |
8 files changed, 255 insertions, 31 deletions
diff --git a/app/diagnostics.go b/app/diagnostics.go index 966cdb5be..dc0ab4ce8 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{}{ diff --git a/app/import.go b/app/import.go index 64e53fe93..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 @@ -1200,7 +1236,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 { 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) { 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) 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 != "" { |