summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorJoramWilander <jwawilander@gmail.com>2018-06-30 12:24:04 -0400
committerJoramWilander <jwawilander@gmail.com>2018-06-30 12:24:04 -0400
commit3848cb7e79e019e2f0878d6e2377ad36b3c7ca43 (patch)
tree98beeb962ffc6d7c3f8cd38651eaccab4a9685fa /app
parent88c5e469ca869d9e8ceadb0f2b03e86005102f24 (diff)
parent2362a5b7f7c2b4bf2b35b104e8616d847b33b0be (diff)
downloadchat-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.go15
-rw-r--r--app/import.go50
-rw-r--r--app/import_test.go120
-rw-r--r--app/server.go24
-rw-r--r--app/session.go3
-rw-r--r--app/status.go16
-rw-r--r--app/web_conn.go18
-rw-r--r--app/web_hub.go40
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 != "" {