From 2114e96f0045376f41d5c318f7cf45b6b50141dc Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Wed, 27 Jun 2018 17:19:06 -0400 Subject: [PATCH 01/10] 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(-) diff --git a/app/session.go b/app/session.go index 53170cec05..5289aefaa0 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 e2367a396d..460cbbbd00 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 9d8134f34a..dd01a8e31d 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 2ce78b5efb..5bb86ee382 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 != "" { 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: [PATCH 02/10] Add english missed texts to i18n/en.json (#8987) * Add English missed texts to i18n/en.json * Update en.json --- app/import.go | 2 +- i18n/en.json | 704 +++------------------- store/sqlstore/user_access_token_store.go | 2 +- 3 files changed, 90 insertions(+), 618 deletions(-) diff --git a/app/import.go b/app/import.go index 64e53fe930..df20b2ba79 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 { diff --git a/i18n/en.json b/i18n/en.json index 7e08ad4292..6c7b444436 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,11 +1,11 @@ [ { "id": "actiance.xml.output.formatter.marshalToXml.appError", - "translation": "" + "translation": "Unable to convert export to XML." }, { "id": "api.admin.add_certificate.array.app_error", - "translation": "" + "translation": "No file under 'certificate' in request." }, { "id": "api.admin.add_certificate.no_file.app_error", @@ -37,7 +37,7 @@ }, { "id": "api.admin.saml.not_available.app_error", - "translation": "" + "translation": "SAML 2.0 is not configured or supported on this server." }, { "id": "api.admin.test_email.body", @@ -189,7 +189,7 @@ }, { "id": "api.channel.post_channel_privacy_message.error", - "translation": "" + "translation": "Failed to post channel privacy update message." }, { "id": "api.channel.post_update_channel_displayname_message_and_forget.create_post.error", @@ -253,7 +253,7 @@ }, { "id": "api.channel.update_channel_scheme.license.error", - "translation": "License does not support updating a channel's scheme" + "translation": "Your license does not support updating a channel's scheme" }, { "id": "api.channel.update_channel_scheme.scheme_scope.error", @@ -962,7 +962,7 @@ }, { "id": "api.emoji.create.other_user.app_error", - "translation": "" + "translation": "Invalid user id" }, { "id": "api.emoji.create.parse.app_error", @@ -1010,7 +1010,7 @@ }, { "id": "api.emoji.upload.open.app_error", - "translation": "" + "translation": "Unable to create the emoji. An error ocurred when trying to open the attached image." }, { "id": "api.file.attachments.disabled.app_error", @@ -1266,7 +1266,7 @@ }, { "id": "api.outgoing_webhook.disabled.app_error", - "translation": "" + "translation": "Outgoing webhooks have been disabled by the system admin." }, { "id": "api.plugin.upload.array.app_error", @@ -1400,15 +1400,15 @@ }, { "id": "api.preference.delete_preferences.delete.app_error", - "translation": "" + "translation": "Unable to delete user preferences." }, { "id": "api.preference.preferences_category.get.app_error", - "translation": "" + "translation": "Unable to get user preferences." }, { "id": "api.preference.update_preferences.set.app_error", - "translation": "" + "translation": "Unable to set user preferences." }, { "id": "api.reaction.save_reaction.invalid.app_error", @@ -1420,15 +1420,15 @@ }, { "id": "api.roles.patch_roles.license.error", - "translation": "Your current license does not support advanced permissions." + "translation": "Your license does not support advanced permissions." }, { "id": "api.scheme.create_scheme.license.error", - "translation": "" + "translation": "Your license does not support creating permissions schemes." }, { "id": "api.scheme.delete_scheme.license.error", - "translation": "" + "translation": "Your license not support delete permissions schemes" }, { "id": "api.scheme.get_channels_for_scheme.scope.error", @@ -1440,7 +1440,7 @@ }, { "id": "api.scheme.patch_scheme.license.error", - "translation": "" + "translation": "Your license does not support update permissions schemes" }, { "id": "api.server.start_server.forward80to443.disabled_while_using_lets_encrypt", @@ -1560,11 +1560,11 @@ }, { "id": "api.team.get_team_icon.filesettings_no_driver.app_error", - "translation": "" + "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'" }, { "id": "api.team.get_team_icon.read_file.app_error", - "translation": "" + "translation": "Unable to read the team icon file." }, { "id": "api.team.import_team.array.app_error", @@ -1696,7 +1696,7 @@ }, { "id": "api.team.update_team_scheme.license.error", - "translation": "License does not support updating a team's scheme" + "translation": "Your license does not support updating a team's scheme" }, { "id": "api.team.update_team_scheme.scheme_scope.error", @@ -2036,7 +2036,7 @@ }, { "id": "api.user.email_to_oauth.not_available.app_error", - "translation": "" + "translation": "Authentication Transfer not configured or available on this server." }, { "id": "api.user.generate_mfa_qr.not_available.app_error", @@ -2104,19 +2104,19 @@ }, { "id": "api.user.oauth_to_email.not_available.app_error", - "translation": "" + "translation": "Authentication Transfer not configured or available on this server." }, { "id": "api.user.reset_password.broken_token.app_error", - "translation": "" + "translation": "The reset password token does not appear to be valid." }, { "id": "api.user.reset_password.invalid_link.app_error", - "translation": "The reset password link does not appear to be valid" + "translation": "The reset password link does not appear to be valid." }, { "id": "api.user.reset_password.link_expired.app_error", - "translation": "The password reset link has expired" + "translation": "The password reset link has expired." }, { "id": "api.user.reset_password.method", @@ -2148,7 +2148,7 @@ }, { "id": "api.user.send_mfa_change_email.error", - "translation": "" + "translation": "Unable to send email notification for MFA change." }, { "id": "api.user.send_password_change_email_and_forget.error", @@ -2264,7 +2264,7 @@ }, { "id": "api.user.verify_email.broken_token.app_error", - "translation": "" + "translation": "Bad verify email token type." }, { "id": "api.web_socket.connect.upgrade.app_error", @@ -2272,19 +2272,19 @@ }, { "id": "api.web_socket_router.bad_action.app_error", - "translation": "" + "translation": "Unknown WebSocket action." }, { "id": "api.web_socket_router.bad_seq.app_error", - "translation": "" + "translation": "Invalid sequence for WebSocket message." }, { "id": "api.web_socket_router.no_action.app_error", - "translation": "" + "translation": "No websocket action." }, { "id": "api.web_socket_router.not_authenticated.app_error", - "translation": "" + "translation": "WebSocket connection is not authenticated. Please log in and try again." }, { "id": "api.webhook.create_outgoing.intersect.app_error", @@ -2360,7 +2360,7 @@ }, { "id": "app.cluster.404.app_error", - "translation": "" + "translation": "Cluster API endpoint not found." }, { "id": "app.import.bulk_import.file_scan.error", @@ -2372,7 +2372,7 @@ }, { "id": "app.import.bulk_import.unsupported_version.error", - "translation": "" + "translation": "Incorrect or missing version in the data import file. Make sure version is the first object in your import file and try again." }, { "id": "app.import.import_channel.scheme_deleted.error", @@ -2484,7 +2484,7 @@ }, { "id": "app.import.import_user.save_preferences.error", - "translation": "" + "translation": "Error importing user preferences. Failed to save preferences." }, { "id": "app.import.import_user_channels.save_preferences.error", @@ -2492,7 +2492,7 @@ }, { "id": "app.import.process_import_data_file_version_line.invalid_version.error", - "translation": "" + "translation": "Unable to read the version of the data import file." }, { "id": "app.import.validate_channel_import_data.display_name_length.error", @@ -2808,7 +2808,7 @@ }, { "id": "app.import.validate_user_import_data.notify_props_comments_trigger_invalid.error", - "translation": "" + "translation": "Invalid Comments Prop value for user." }, { "id": "app.import.validate_user_import_data.notify_props_desktop_invalid.error", @@ -2832,10 +2832,6 @@ }, { "id": "app.import.validate_user_import_data.password_length.error", - "translation": "" - }, - { - "id": "app.import.validate_user_import_data.pasword_length.error", "translation": "User Password has invalid length." }, { @@ -2960,7 +2956,7 @@ }, { "id": "app.plugin.get_cluster_plugin_statuses.app_error", - "translation": "" + "translation": "Unable to get plugin statuses from the cluster." }, { "id": "app.plugin.get_plugins.app_error", @@ -3020,7 +3016,7 @@ }, { "id": "app.user.complete_switch_with_oauth.blank_email.app_error", - "translation": "" + "translation": "Unable to complete SAML login with an empty email address." }, { "id": "app.user_access_token.disabled", @@ -3030,549 +3026,29 @@ "id": "app.user_access_token.invalid_or_missing", "translation": "Invalid or missing token" }, - { - "id": "authentication.permissions.add_reaction.description", - "translation": "" - }, - { - "id": "authentication.permissions.add_reaction.name", - "translation": "" - }, - { - "id": "authentication.permissions.add_user_to_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.add_user_to_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.assign_system_admin_role.description", - "translation": "" - }, - { - "id": "authentication.permissions.assign_system_admin_role.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_direct_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_direct_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_group_channel.description", - "translation": "Ability to create new group message channels" - }, - { - "id": "authentication.permissions.create_group_channel.name", - "translation": "Create Group Message" - }, - { - "id": "authentication.permissions.create_post.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_post.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_post_ephemeral.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_post_ephemeral.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_post_public.description", - "translation": "Ability to create posts in public channels" - }, - { - "id": "authentication.permissions.create_post_public.name", - "translation": "Create Posts in Public Channels" - }, - { - "id": "authentication.permissions.create_private_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_private_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_public_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_public_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.create_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.create_user_access_token.description", - "translation": "Ability to create personal access tokens" - }, - { - "id": "authentication.permissions.create_user_access_token.name", - "translation": "Create Personal Access Token" - }, - { - "id": "authentication.permissions.delete_others_posts.description", - "translation": "" - }, - { - "id": "authentication.permissions.delete_others_posts.name", - "translation": "" - }, - { - "id": "authentication.permissions.delete_post.description", - "translation": "" - }, - { - "id": "authentication.permissions.delete_post.name", - "translation": "" - }, - { - "id": "authentication.permissions.delete_private_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.delete_private_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.delete_public_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.delete_public_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.edit_other_users.description", - "translation": "" - }, - { - "id": "authentication.permissions.edit_other_users.name", - "translation": "" - }, - { - "id": "authentication.permissions.edit_others_posts.description", - "translation": "" - }, - { - "id": "authentication.permissions.edit_others_posts.name", - "translation": "" - }, - { - "id": "authentication.permissions.edit_post.description", - "translation": "" - }, - { - "id": "authentication.permissions.edit_post.name", - "translation": "" - }, - { - "id": "authentication.permissions.get_public_link.description", - "translation": "" - }, - { - "id": "authentication.permissions.get_public_link.name", - "translation": "" - }, - { - "id": "authentication.permissions.import_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.import_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.join_public_channels.description", - "translation": "" - }, - { - "id": "authentication.permissions.join_public_channels.name", - "translation": "" - }, - { - "id": "authentication.permissions.list_team_channels.description", - "translation": "" - }, - { - "id": "authentication.permissions.list_team_channels.name", - "translation": "" - }, - { - "id": "authentication.permissions.list_users_without_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.list_users_without_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_channel_roles.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_channel_roles.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_emojis.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_emojis.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_oauth.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_oauth.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_emojis.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_emojis.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_slash_commands.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_slash_commands.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_webhooks.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_others_webhooks.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_private_channel_members.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_private_channel_members.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_private_channel_properties.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_private_channel_properties.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_public_channel_members.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_public_channel_members.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_public_channel_properties.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_public_channel_properties.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_roles.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_roles.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_slash_commands.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_slash_commands.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_system.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_system.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_system_wide_oauth.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_system_wide_oauth.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.manage_team_roles.description", - "translation": "Ability to change the roles of a team member" - }, - { - "id": "authentication.permissions.manage_team_roles.name", - "translation": "Manage Team Roles" - }, - { - "id": "authentication.permissions.manage_webhooks.description", - "translation": "" - }, - { - "id": "authentication.permissions.manage_webhooks.name", - "translation": "" - }, - { - "id": "authentication.permissions.permanent_delete_user.description", - "translation": "" - }, - { - "id": "authentication.permissions.permanent_delete_user.name", - "translation": "" - }, - { - "id": "authentication.permissions.read_channel.description", - "translation": "" - }, - { - "id": "authentication.permissions.read_channel.name", - "translation": "" - }, - { - "id": "authentication.permissions.read_public_channel.description", - "translation": "Ability to read public channels" - }, - { - "id": "authentication.permissions.read_public_channel.name", - "translation": "Read Public Channels" - }, - { - "id": "authentication.permissions.read_user_access_token.description", - "translation": "Ability to read personal access tokens' id, description and user_id fields" - }, - { - "id": "authentication.permissions.read_user_access_token.name", - "translation": "Read Personal Access Tokens" - }, - { - "id": "authentication.permissions.remove_others_reactions.description", - "translation": "" - }, - { - "id": "authentication.permissions.remove_others_reactions.name", - "translation": "" - }, - { - "id": "authentication.permissions.remove_reaction.description", - "translation": "" - }, - { - "id": "authentication.permissions.remove_reaction.name", - "translation": "" - }, - { - "id": "authentication.permissions.remove_user_from_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.remove_user_from_team.name", - "translation": "" - }, - { - "id": "authentication.permissions.revoke_user_access_token.description", - "translation": "Ability to revoke personal access tokens" - }, - { - "id": "authentication.permissions.revoke_user_access_token.name", - "translation": "Revoke Personal Access Token" - }, - { - "id": "authentication.permissions.team_invite_user.description", - "translation": "Ability to invite users to a team" - }, - { - "id": "authentication.permissions.team_invite_user.name", - "translation": "Invite User" - }, - { - "id": "authentication.permissions.team_use_slash_commands.description", - "translation": "Ability to use slash commands" - }, - { - "id": "authentication.permissions.team_use_slash_commands.name", - "translation": "Use Slash Commands" - }, - { - "id": "authentication.permissions.upload_file.description", - "translation": "" - }, - { - "id": "authentication.permissions.upload_file.name", - "translation": "" - }, - { - "id": "authentication.permissions.view_team.description", - "translation": "" - }, - { - "id": "authentication.permissions.view_team.name", - "translation": "" - }, - { - "id": "authentication.permisssions.manage_jobs.description", - "translation": "" - }, - { - "id": "authentication.permisssions.manage_jobs.name", - "translation": "" - }, - { - "id": "authentication.roles.channel_admin.description", - "translation": "" - }, - { - "id": "authentication.roles.channel_admin.name", - "translation": "" - }, - { - "id": "authentication.roles.channel_user.description", - "translation": "" - }, - { - "id": "authentication.roles.channel_user.name", - "translation": "" - }, - { - "id": "authentication.roles.global_admin.description", - "translation": "" - }, - { - "id": "authentication.roles.global_admin.name", - "translation": "" - }, - { - "id": "authentication.roles.global_user.description", - "translation": "" - }, - { - "id": "authentication.roles.global_user.name", - "translation": "" - }, - { - "id": "authentication.roles.system_post_all.description", - "translation": "A role with the permission to post in any public, private or direct channel on the system" - }, - { - "id": "authentication.roles.system_post_all.name", - "translation": "Post in Public, Private and Direct Channels" - }, - { - "id": "authentication.roles.system_post_all_public.description", - "translation": "A role with the permission to post in any public channel on the system" - }, - { - "id": "authentication.roles.system_post_all_public.name", - "translation": "Post in Public Channels" - }, - { - "id": "authentication.roles.system_user_access_token.description", - "translation": "A role with the permissions to create, read and revoke personal access tokens" - }, - { - "id": "authentication.roles.system_user_access_token.name", - "translation": "Personal Access Token" - }, - { - "id": "authentication.roles.team_admin.description", - "translation": "" - }, - { - "id": "authentication.roles.team_admin.name", - "translation": "" - }, - { - "id": "authentication.roles.team_post_all.description", - "translation": "A role with the permission to post in any public or private channel on the team" - }, - { - "id": "authentication.roles.team_post_all.name", - "translation": "Post in Public and Private Channels" - }, - { - "id": "authentication.roles.team_post_all_public.description", - "translation": "A role with the permission to post in any public channel on the team" - }, - { - "id": "authentication.roles.team_post_all_public.name", - "translation": "Post in Public Channels" - }, - { - "id": "authentication.roles.team_user.description", - "translation": "" - }, - { - "id": "authentication.roles.team_user.name", - "translation": "" - }, { "id": "brand.save_brand_image.decode.app_error", - "translation": "" + "translation": "Unable to decode the image data." }, { "id": "brand.save_brand_image.decode_config.app_error", - "translation": "" + "translation": "Unable to get image metadata." }, { "id": "brand.save_brand_image.encode.app_error", - "translation": "" + "translation": "Unable to convert the image data to PNG format. Please try again." }, { "id": "brand.save_brand_image.open.app_error", - "translation": "" + "translation": "Unable to upload the custom brand image. Make sure the image size is less than 2 MB and try again." }, { "id": "brand.save_brand_image.save_image.app_error", - "translation": "" + "translation": "Unable to write the image file to your file storage. Please check your connection and try again." }, { "id": "brand.save_brand_image.too_large.app_error", - "translation": "" + "translation": "Unable to read the image file. Make sure the image size is less than 2 MB and try again." }, { "id": "cli.license.critical", @@ -3580,11 +3056,11 @@ }, { "id": "ent.account_migration.get_all_failed", - "translation": "" + "translation": "Unable to get users." }, { "id": "ent.account_migration.get_saml_users_failed", - "translation": "" + "translation": "Unable to get SAML users." }, { "id": "ent.cluster.config_changed.info", @@ -3620,19 +3096,19 @@ }, { "id": "ent.compliance.csv.metadata.json.marshalling.appError", - "translation": "" + "translation": "Unable to convert metadata to json." }, { "id": "ent.compliance.csv.post.export.appError", - "translation": "" + "translation": "Unable to export a post." }, { "id": "ent.compliance.csv.zip.creation.appError", - "translation": "" + "translation": "Unable to create the zip export file." }, { "id": "ent.compliance.global_relay.attachments_removed.appError", - "translation": "" + "translation": "Uploaded file was removed from Global Relay export because it was too large to send." }, { "id": "ent.compliance.licence_disable.app_error", @@ -3640,7 +3116,7 @@ }, { "id": "ent.compliance.run_export.template_watcher.appError", - "translation": "" + "translation": "Unable to load export templates. Please try again." }, { "id": "ent.compliance.run_failed.error", @@ -3648,7 +3124,7 @@ }, { "id": "ent.data_retention.generic.license.error", - "translation": "License does not support Data Retention." + "translation": "Your license does not support Data Retention." }, { "id": "ent.elasticsearch.aggregator_worker.create_index_job.error", @@ -3748,7 +3224,7 @@ }, { "id": "ent.elasticsearch.test_config.license.error", - "translation": "License does not support Elasticsearch." + "translation": "Your license does not support Elasticsearch." }, { "id": "ent.elasticsearch.test_config.reenter_password", @@ -3800,7 +3276,7 @@ }, { "id": "ent.ldap.syncronize.search_failure.app_error", - "translation": "" + "translation": "Failed to search users in AD/LDAP. Test if the Mattermost server can connect to your AD/LDAP server and try again." }, { "id": "ent.ldap.validate_filter.app_error", @@ -3924,11 +3400,11 @@ }, { "id": "jobs.do_job.batch_size.parse_error", - "translation": "" + "translation": "Could not parse message export job BatchSize." }, { "id": "jobs.do_job.batch_start_timestamp.parse_error", - "translation": "" + "translation": "Could not parse message export job ExportFromTimestamp." }, { "id": "jobs.request_cancellation.status.error", @@ -3940,7 +3416,7 @@ }, { "id": "jobs.start_synchronize_job.timeout", - "translation": "" + "translation": "Reached AD/LDAP sychronization job timeout." }, { "id": "manaultesting.manual_test.parse.app_error", @@ -4104,7 +3580,7 @@ }, { "id": "model.client.get_team_icon.app_error", - "translation": "" + "translation": "Unable to read the team icon from the body response." }, { "id": "model.client.read_file.app_error", @@ -4120,11 +3596,11 @@ }, { "id": "model.client.set_team_icon.no_file.app_error", - "translation": "" + "translation": "No file under 'image' in request." }, { "id": "model.client.set_team_icon.writer.app_error", - "translation": "" + "translation": "Unable to write the request." }, { "id": "model.client.upload_post_attachment.channel_id.app_error", @@ -4632,7 +4108,7 @@ }, { "id": "model.emoji.user_id.app_error", - "translation": "" + "translation": "Invalid creator id" }, { "id": "model.file_info.get.gif.app_error", @@ -4640,27 +4116,27 @@ }, { "id": "model.file_info.is_valid.create_at.app_error", - "translation": "" + "translation": "Invalid value for create_at." }, { "id": "model.file_info.is_valid.id.app_error", - "translation": "" + "translation": "Invalid value for id." }, { "id": "model.file_info.is_valid.path.app_error", - "translation": "" + "translation": "Invalid value for path." }, { "id": "model.file_info.is_valid.post_id.app_error", - "translation": "" + "translation": "Invalid value for post_id." }, { "id": "model.file_info.is_valid.update_at.app_error", - "translation": "" + "translation": "Invalid value for update_at." }, { "id": "model.file_info.is_valid.user_id.app_error", - "translation": "" + "translation": "Invalid value for user_id." }, { "id": "model.incoming_hook.channel_id.app_error", @@ -4724,11 +4200,11 @@ }, { "id": "model.license_record.is_valid.create_at.app_error", - "translation": "" + "translation": "Invalid value for create_at when uploading a license." }, { "id": "model.license_record.is_valid.id.app_error", - "translation": "" + "translation": "Invalid value for id when uploading a license." }, { "id": "model.oauth.is_valid.app_id.app_error", @@ -4780,7 +4256,7 @@ }, { "id": "model.outgoing_hook.is_valid.content_type.app_error", - "translation": "" + "translation": "Invalid value for content_type" }, { "id": "model.outgoing_hook.is_valid.create_at.app_error", @@ -5008,7 +4484,7 @@ }, { "id": "model.user_access_token.is_valid.id.app_error", - "translation": "" + "translation": "Invalid value for id" }, { "id": "model.user_access_token.is_valid.token.app_error", @@ -5024,7 +4500,7 @@ }, { "id": "model.websocket_client.connect_fail.app_error", - "translation": "" + "translation": "Unable to connect to the WebSocket server." }, { "id": "oauth.gitlab.tos.error", @@ -5144,11 +4620,11 @@ }, { "id": "store.sql_channel.get_deleted.existing.app_error", - "translation": "" + "translation": "We couldn't find the existing deleted channel" }, { "id": "store.sql_channel.get_deleted.missing.app_error", - "translation": "" + "translation": "No deleted channels exist" }, { "id": "store.sql_channel.get_deleted_by_name.existing.app_error", @@ -5672,7 +5148,7 @@ }, { "id": "store.sql_post.compliance_export.app_error", - "translation": "" + "translation": "We couldn't get the compliance export posts." }, { "id": "store.sql_post.delete.app_error", @@ -5684,7 +5160,7 @@ }, { "id": "store.sql_post.get_flagged_posts.app_error", - "translation": "" + "translation": "We couldn't get the flagged posts" }, { "id": "store.sql_post.get_parents_posts.app_error", @@ -5832,7 +5308,7 @@ }, { "id": "store.sql_reaction.delete.app_error", - "translation": "" + "translation": "Unable to delete reaction" }, { "id": "store.sql_reaction.delete.begin.app_error", @@ -5844,11 +5320,11 @@ }, { "id": "store.sql_reaction.delete_all_with_emoji_name.delete_reactions.app_error", - "translation": "" + "translation": "Unable to delete all reactions with this emoji name" }, { "id": "store.sql_reaction.delete_all_with_emoji_name.get_reactions.app_error", - "translation": "" + "translation": "Unable to get all reactions with this emoji name" }, { "id": "store.sql_reaction.get_for_post.app_error", @@ -5872,15 +5348,15 @@ }, { "id": "store.sql_recover.delete.app_error", - "translation": "" + "translation": "Unable to delete token" }, { "id": "store.sql_recover.get_by_code.app_error", - "translation": "" + "translation": "Unable to get a token with this code" }, { "id": "store.sql_recover.save.app_error", - "translation": "" + "translation": "Unable to save the token" }, { "id": "store.sql_role.delete.update.app_error", @@ -6052,7 +5528,7 @@ }, { "id": "store.sql_status.update_last_activity_at.app_error", - "translation": "" + "translation": "Unable to update the last activity date and time of the user" }, { "id": "store.sql_system.get.app_error", @@ -6236,11 +5712,11 @@ }, { "id": "store.sql_team.update_last_team_icon_update.app_error", - "translation": "" + "translation": "We couldn't update the date of the last team icon update" }, { "id": "store.sql_user.analytics_daily_active_users.app_error", - "translation": "" + "translation": "We couldn't get the active users during the requested period" }, { "id": "store.sql_user.analytics_get_inactive_users_count.app_error", @@ -6376,7 +5852,7 @@ }, { "id": "store.sql_user.search.app_error", - "translation": "" + "translation": "We couldn't find any user maching the search parameters" }, { "id": "store.sql_user.update.app_error", @@ -6436,7 +5912,7 @@ }, { "id": "store.sql_user.update_update.app_error", - "translation": "" + "translation": "We couldn't update the date of the last update of the user" }, { "id": "store.sql_user.verify_email.app_error", @@ -6472,15 +5948,11 @@ }, { "id": "store.sql_user_access_token.update_token_disable.app_error", - "translation": "" - }, - { - "id": "store.sql_user_access_token.update_token_disble.app_error", - "translation": "" + "translation": "We couldn't disable the access token" }, { "id": "store.sql_user_access_token.update_token_enable.app_error", - "translation": "" + "translation": "We couldn't enable the access token" }, { "id": "store.sql_webhooks.analytics_incoming_count.app_error", @@ -6680,7 +6152,7 @@ }, { "id": "web.get_access_token.internal_saving.app_error", - "translation": "" + "translation": "We couldn't update the user access data." }, { "id": "web.incoming_webhook.channel.app_error", diff --git a/store/sqlstore/user_access_token_store.go b/store/sqlstore/user_access_token_store.go index b90ba773fc..5df0870264 100644 --- a/store/sqlstore/user_access_token_store.go +++ b/store/sqlstore/user_access_token_store.go @@ -245,7 +245,7 @@ func (s SqlUserAccessTokenStore) UpdateTokenDisable(tokenId string) store.StoreC return store.Do(func(result *store.StoreResult) { transaction, err := s.GetMaster().Begin() if err != nil { - result.Err = model.NewAppError("SqlUserAccessTokenStore.UpdateTokenDisable", "store.sql_user_access_token.update_token_disble.app_error", nil, err.Error(), http.StatusInternalServerError) + result.Err = model.NewAppError("SqlUserAccessTokenStore.UpdateTokenDisable", "store.sql_user_access_token.update_token_disable.app_error", nil, err.Error(), http.StatusInternalServerError) } else { if extrasResult := s.deleteSessionsAndDisableToken(transaction, tokenId); extrasResult.Err != nil { *result = extrasResult From 4b2a6263547363c43dba9c320c077ad295caaf4d Mon Sep 17 00:00:00 2001 From: Jerry Kurian Date: Thu, 28 Jun 2018 09:45:20 -0400 Subject: [PATCH 03/10] 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 +++++---- config/default.json | 1 + i18n/en.json | 58 ++++++++++++++++++--------------- model/config.go | 27 ++++++++++----- store/sqlstore/supplier.go | 3 +- store/sqlstore/supplier_test.go | 32 ++++++++++-------- store/storetest/docker.go | 20 +++++++----- 7 files changed, 88 insertions(+), 68 deletions(-) diff --git a/app/diagnostics.go b/app/diagnostics.go index 3df62c41d7..e9b3405be3 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/config/default.json b/config/default.json index 2d1e6ceecf..6e4f6d1eb6 100644 --- a/config/default.json +++ b/config/default.json @@ -119,6 +119,7 @@ "DataSourceReplicas": [], "DataSourceSearchReplicas": [], "MaxIdleConns": 20, + "ConnMaxLifetimeMilliseconds": 3600000, "MaxOpenConns": 300, "Trace": false, "AtRestEncryptKey": "", diff --git a/i18n/en.json b/i18n/en.json index 6c7b444436..fd9888e39b 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -3064,11 +3064,11 @@ }, { "id": "ent.cluster.config_changed.info", - "translation": "Cluster configuration has changed for id={{ .id }}. The cluster may become unstable and a restart is required. To ensure the cluster is configured correctly you should perform a rolling restart immediately." + "translation": "Cluster configuration has changed for id={{ .id }}. The cluster may become unstable and a restart is required. To ensure the cluster is configured correctly you should perform a rolling restart immediately." }, { "id": "ent.cluster.save_config.error", - "translation": "System Console is set to read-only when High Availability is enabled unless ReadOnlyConfig is disabled in the configuration file." + "translation": "System Console is set to read-only when High Availability is enabled unless ReadOnlyConfig is disabled in the configuration file." }, { "id": "ent.compliance.bad_export_type.appError", @@ -3812,11 +3812,11 @@ }, { "id": "model.config.is_valid.email_batching_buffer_size.app_error", - "translation": "Invalid email batching buffer size for email settings. Must be zero or a positive number." + "translation": "Invalid email batching buffer size for email settings. Must be zero or a positive number." }, { "id": "model.config.is_valid.email_batching_interval.app_error", - "translation": "Invalid email batching interval for email settings. Must be 30 seconds or more." + "translation": "Invalid email batching interval for email settings. Must be 30 seconds or more." }, { "id": "model.config.is_valid.email_notification_contents_type.app_error", @@ -3824,23 +3824,23 @@ }, { "id": "model.config.is_valid.email_salt.app_error", - "translation": "Invalid invite salt for email settings. Must be 32 chars or more." + "translation": "Invalid invite salt for email settings. Must be 32 chars or more." }, { "id": "model.config.is_valid.email_security.app_error", - "translation": "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'" + "translation": "Invalid connection security for email settings. Must be '', 'TLS', or 'STARTTLS'" }, { "id": "model.config.is_valid.encrypt_sql.app_error", - "translation": "Invalid at rest encrypt key for SQL settings. Must be 32 chars or more." + "translation": "Invalid at rest encrypt key for SQL settings. Must be 32 chars or more." }, { "id": "model.config.is_valid.file_driver.app_error", - "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'" + "translation": "Invalid driver name for file settings. Must be 'local' or 'amazons3'" }, { "id": "model.config.is_valid.file_salt.app_error", - "translation": "Invalid public link salt for file settings. Must be 32 chars or more." + "translation": "Invalid public link salt for file settings. Must be 32 chars or more." }, { "id": "model.config.is_valid.group_unread_channels.app_error", @@ -3872,7 +3872,7 @@ }, { "id": "model.config.is_valid.ldap_security.app_error", - "translation": "Invalid connection security for AD/LDAP settings. Must be '', 'TLS', or 'STARTTLS'" + "translation": "Invalid connection security for AD/LDAP settings. Must be '', 'TLS', or 'STARTTLS'" }, { "id": "model.config.is_valid.ldap_server", @@ -3896,7 +3896,7 @@ }, { "id": "model.config.is_valid.login_attempts.app_error", - "translation": "Invalid maximum login attempts for service settings. Must be a positive number." + "translation": "Invalid maximum login attempts for service settings. Must be a positive number." }, { "id": "model.config.is_valid.max_burst.app_error", @@ -3904,7 +3904,7 @@ }, { "id": "model.config.is_valid.max_channels.app_error", - "translation": "Invalid maximum channels per team for team settings. Must be a positive number." + "translation": "Invalid maximum channels per team for team settings. Must be a positive number." }, { "id": "model.config.is_valid.max_file_size.app_error", @@ -3912,11 +3912,11 @@ }, { "id": "model.config.is_valid.max_notify_per_channel.app_error", - "translation": "Invalid maximum notifications per channel for team settings. Must be a positive number." + "translation": "Invalid maximum notifications per channel for team settings. Must be a positive number." }, { "id": "model.config.is_valid.max_users.app_error", - "translation": "Invalid maximum users per team for team settings. Must be a positive number." + "translation": "Invalid maximum users per team for team settings. Must be a positive number." }, { "id": "model.config.is_valid.message_export.batch_size.app_error", @@ -3964,11 +3964,11 @@ }, { "id": "model.config.is_valid.rate_mem.app_error", - "translation": "Invalid memory store size for rate limit settings. Must be a positive number" + "translation": "Invalid memory store size for rate limit settings. Must be a positive number" }, { "id": "model.config.is_valid.rate_sec.app_error", - "translation": "Invalid per sec for rate limit settings. Must be a positive number" + "translation": "Invalid per sec for rate limit settings. Must be a positive number" }, { "id": "model.config.is_valid.read_timeout.app_error", @@ -3976,7 +3976,7 @@ }, { "id": "model.config.is_valid.restrict_direct_message.app_error", - "translation": "Invalid direct message restriction. Must be 'any', or 'team'" + "translation": "Invalid direct message restriction. Must be 'any', or 'team'" }, { "id": "model.config.is_valid.saml_assertion_consumer_service_url.app_error", @@ -4024,27 +4024,31 @@ }, { "id": "model.config.is_valid.sql_data_src.app_error", - "translation": "Invalid data source for SQL settings. Must be set." + "translation": "Invalid data source for SQL settings. Must be set." }, { "id": "model.config.is_valid.sql_driver.app_error", - "translation": "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'" + "translation": "Invalid driver name for SQL settings. Must be 'mysql' or 'postgres'" }, { "id": "model.config.is_valid.sql_idle.app_error", - "translation": "Invalid maximum idle connection for SQL settings. Must be a positive number." + "translation": "Invalid maximum idle connection for SQL settings. Must be a positive number." }, { "id": "model.config.is_valid.sql_max_conn.app_error", - "translation": "Invalid maximum open connection for SQL settings. Must be a positive number." + "translation": "Invalid maximum open connection for SQL settings. Must be a positive number." + }, + { + "id": "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error", + "translation": "Invalid connection maximum lifetime for SQL settings. Must be a non-negative number." }, { "id": "model.config.is_valid.sql_query_timeout.app_error", - "translation": "Invalid query timeout for SQL settings. Must be a positive number." + "translation": "Invalid query timeout for SQL settings. Must be a positive number." }, { "id": "model.config.is_valid.teammate_name_display.app_error", - "translation": "Invalid teammate display. Must be 'full_name', 'nickname_full_name' or 'username'" + "translation": "Invalid teammate display. Must be 'full_name', 'nickname_full_name' or 'username'" }, { "id": "model.config.is_valid.time_between_user_typing.app_error", @@ -6040,7 +6044,7 @@ }, { "id": "utils.config.add_client_locale.app_error", - "translation": "Unable to load mattermost configuration file: Adding DefaultClientLocale to AvailableLocales." + "translation": "Unable to load mattermost configuration file: Adding DefaultClientLocale to AvailableLocales." }, { "id": "utils.config.load_config.decoding.panic", @@ -6056,15 +6060,15 @@ }, { "id": "utils.config.supported_available_locales.app_error", - "translation": "Unable to load mattermost configuration file: AvailableLocales must include DefaultClientLocale. Setting AvailableLocales to all locales as default value." + "translation": "Unable to load mattermost configuration file: AvailableLocales must include DefaultClientLocale. Setting AvailableLocales to all locales as default value." }, { "id": "utils.config.supported_client_locale.app_error", - "translation": "Unable to load mattermost configuration file: DefaultClientLocale must be one of the supported locales. Setting DefaultClientLocale to en as default value." + "translation": "Unable to load mattermost configuration file: DefaultClientLocale must be one of the supported locales. Setting DefaultClientLocale to en as default value." }, { "id": "utils.config.supported_server_locale.app_error", - "translation": "Unable to load mattermost configuration file: DefaultServerLocale must be one of the supported locales. Setting DefaultServerLocale to en as default value." + "translation": "Unable to load mattermost configuration file: DefaultServerLocale must be one of the supported locales. Setting DefaultServerLocale to en as default value." }, { "id": "utils.file.list_directory.local.app_error", diff --git a/model/config.go b/model/config.go index ce66f2f052..e6bd04dfc4 100644 --- a/model/config.go +++ b/model/config.go @@ -605,15 +605,16 @@ type SSOSettings struct { } type SqlSettings struct { - DriverName *string - DataSource *string - DataSourceReplicas []string - DataSourceSearchReplicas []string - MaxIdleConns *int - MaxOpenConns *int - Trace bool - AtRestEncryptKey string - QueryTimeout *int + DriverName *string + DataSource *string + DataSourceReplicas []string + DataSourceSearchReplicas []string + MaxIdleConns *int + ConnMaxLifetimeMilliseconds *int + MaxOpenConns *int + Trace bool + AtRestEncryptKey string + QueryTimeout *int } func (s *SqlSettings) SetDefaults() { @@ -637,6 +638,10 @@ func (s *SqlSettings) SetDefaults() { s.MaxOpenConns = NewInt(300) } + if s.ConnMaxLifetimeMilliseconds == nil { + s.ConnMaxLifetimeMilliseconds = NewInt(3600000) + } + if s.QueryTimeout == nil { s.QueryTimeout = NewInt(30) } @@ -2069,6 +2074,10 @@ func (ss *SqlSettings) isValid() *AppError { return NewAppError("Config.IsValid", "model.config.is_valid.sql_idle.app_error", nil, "", http.StatusBadRequest) } + if *ss.ConnMaxLifetimeMilliseconds < 0 { + return NewAppError("Config.IsValid", "model.config.is_valid.sql_conn_max_lifetime_milliseconds.app_error", nil, "", http.StatusBadRequest) + } + if *ss.QueryTimeout <= 0 { return NewAppError("Config.IsValid", "model.config.is_valid.sql_query_timeout.app_error", nil, "", http.StatusBadRequest) } diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go index 1e6bdcba11..6c49d91fb9 100644 --- a/store/sqlstore/supplier.go +++ b/store/sqlstore/supplier.go @@ -28,7 +28,6 @@ import ( const ( INDEX_TYPE_FULL_TEXT = "full_text" INDEX_TYPE_DEFAULT = "default" - MAX_DB_CONN_LIFETIME = 60 DB_PING_ATTEMPTS = 18 DB_PING_TIMEOUT_SECS = 10 ) @@ -218,7 +217,7 @@ func setupConnection(con_type string, dataSource string, settings *model.SqlSett db.SetMaxIdleConns(*settings.MaxIdleConns) db.SetMaxOpenConns(*settings.MaxOpenConns) - db.SetConnMaxLifetime(time.Duration(MAX_DB_CONN_LIFETIME) * time.Minute) + db.SetConnMaxLifetime(time.Duration(*settings.ConnMaxLifetimeMilliseconds) * time.Millisecond) var dbmap *gorp.DbMap diff --git a/store/sqlstore/supplier_test.go b/store/sqlstore/supplier_test.go index 5aacfbe6a2..3835bd01f1 100644 --- a/store/sqlstore/supplier_test.go +++ b/store/sqlstore/supplier_test.go @@ -73,17 +73,19 @@ func TestGetReplica(t *testing.T) { driverName := model.DATABASE_DRIVER_SQLITE dataSource := ":memory:" maxIdleConns := 1 + connMaxLifetimeMilliseconds := 3600000 maxOpenConns := 1 queryTimeout := 5 settings := model.SqlSettings{ - DriverName: &driverName, - DataSource: &dataSource, - MaxIdleConns: &maxIdleConns, - MaxOpenConns: &maxOpenConns, - QueryTimeout: &queryTimeout, - DataSourceReplicas: testCase.DataSourceReplicas, - DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, + DriverName: &driverName, + DataSource: &dataSource, + MaxIdleConns: &maxIdleConns, + ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds, + MaxOpenConns: &maxOpenConns, + QueryTimeout: &queryTimeout, + DataSourceReplicas: testCase.DataSourceReplicas, + DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, } supplier := sqlstore.NewSqlSupplier(settings, nil) @@ -209,17 +211,19 @@ func TestGetAllConns(t *testing.T) { driverName := model.DATABASE_DRIVER_SQLITE dataSource := ":memory:" maxIdleConns := 1 + connMaxLifetimeMilliseconds := 3600000 maxOpenConns := 1 queryTimeout := 5 settings := model.SqlSettings{ - DriverName: &driverName, - DataSource: &dataSource, - MaxIdleConns: &maxIdleConns, - MaxOpenConns: &maxOpenConns, - QueryTimeout: &queryTimeout, - DataSourceReplicas: testCase.DataSourceReplicas, - DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, + DriverName: &driverName, + DataSource: &dataSource, + MaxIdleConns: &maxIdleConns, + ConnMaxLifetimeMilliseconds: &connMaxLifetimeMilliseconds, + MaxOpenConns: &maxOpenConns, + QueryTimeout: &queryTimeout, + DataSourceReplicas: testCase.DataSourceReplicas, + DataSourceSearchReplicas: testCase.DataSourceSearchReplicas, } supplier := sqlstore.NewSqlSupplier(settings, nil) diff --git a/store/storetest/docker.go b/store/storetest/docker.go index f3830a6fed..f5b8807d0e 100644 --- a/store/storetest/docker.go +++ b/store/storetest/docker.go @@ -76,17 +76,19 @@ func NewPostgreSQLContainer() (*RunningContainer, *model.SqlSettings, error) { func databaseSettings(driver, dataSource string) *model.SqlSettings { settings := &model.SqlSettings{ - DriverName: &driver, - DataSource: &dataSource, - DataSourceReplicas: []string{}, - DataSourceSearchReplicas: []string{}, - MaxIdleConns: new(int), - MaxOpenConns: new(int), - Trace: false, - AtRestEncryptKey: model.NewRandomString(32), - QueryTimeout: new(int), + DriverName: &driver, + DataSource: &dataSource, + DataSourceReplicas: []string{}, + DataSourceSearchReplicas: []string{}, + MaxIdleConns: new(int), + ConnMaxLifetimeMilliseconds: new(int), + MaxOpenConns: new(int), + Trace: false, + AtRestEncryptKey: model.NewRandomString(32), + QueryTimeout: new(int), } *settings.MaxIdleConns = 10 + *settings.ConnMaxLifetimeMilliseconds = 3600000 *settings.MaxOpenConns = 100 *settings.QueryTimeout = 10 return settings From 57bf7203f329c47220c457a891244e512ae868e6 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 28 Jun 2018 17:57:42 -0400 Subject: [PATCH 04/10] subpath: rewrite manifest.json too (#9017) --- utils/subpath.go | 10 +- utils/subpath_test.go | 232 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 231 insertions(+), 11 deletions(-) diff --git a/utils/subpath.go b/utils/subpath.go index cddc90fa48..be06a73ef1 100644 --- a/utils/subpath.go +++ b/utils/subpath.go @@ -89,14 +89,14 @@ func UpdateAssetsSubpath(subpath string) error { return errors.Wrapf(err, "failed to update root.html with subpath %s", subpath) } - // Rewrite the *.css references to `/static/*` (or a previously rewritten subpath). + // Rewrite the manifest.json and *.css references to `/static/*` (or a previously rewritten subpath). err = filepath.Walk(staticDir, func(walkPath string, info os.FileInfo, err error) error { - if filepath.Ext(walkPath) == ".css" { - if oldCss, err := ioutil.ReadFile(walkPath); err != nil { + if filepath.Base(walkPath) == "manifest.json" || filepath.Ext(walkPath) == ".css" { + if old, err := ioutil.ReadFile(walkPath); err != nil { return errors.Wrapf(err, "failed to open %s", walkPath) } else { - newCss := strings.Replace(string(oldCss), pathToReplace, newPath, -1) - if err = ioutil.WriteFile(walkPath, []byte(newCss), 0); err != nil { + new := strings.Replace(string(old), pathToReplace, newPath, -1) + if err = ioutil.WriteFile(walkPath, []byte(new), 0); err != nil { return errors.Wrapf(err, "failed to update %s with subpath %s", walkPath, subpath) } } diff --git a/utils/subpath_test.go b/utils/subpath_test.go index ee518d5f6a..6e417e1c51 100644 --- a/utils/subpath_test.go +++ b/utils/subpath_test.go @@ -33,52 +33,64 @@ func TestUpdateAssetsSubpath(t *testing.T) { require.NoError(t, err) testCases := []struct { - Description string - RootHTML string - MainCSS string - Subpath string - ExpectedRootHTML string - ExpectedMainCSS string + Description string + RootHTML string + MainCSS string + ManifestJSON string + Subpath string + ExpectedRootHTML string + ExpectedMainCSS string + ExpectedManifestJSON string }{ { "no changes required, empty subpath provided", baseRootHtml, baseCss, + baseManifestJson, "", baseRootHtml, baseCss, + baseManifestJson, }, { "no changes required", baseRootHtml, baseCss, + baseManifestJson, "/", baseRootHtml, baseCss, + baseManifestJson, }, { "subpath", baseRootHtml, baseCss, + baseManifestJson, "/subpath", subpathRootHtml, subpathCss, + subpathManifestJson, }, { "new subpath from old", subpathRootHtml, subpathCss, + subpathManifestJson, "/nested/subpath", newSubpathRootHtml, newSubpathCss, + newSubpathManifestJson, }, { "resetting to /", subpathRootHtml, subpathCss, + baseManifestJson, "/", resetRootHtml, baseCss, + baseManifestJson, }, } @@ -86,6 +98,7 @@ func TestUpdateAssetsSubpath(t *testing.T) { t.Run(testCase.Description, func(t *testing.T) { ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "root.html"), []byte(testCase.RootHTML), 0700) ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "main.css"), []byte(testCase.MainCSS), 0700) + ioutil.WriteFile(filepath.Join(tempDir, model.CLIENT_DIR, "manifest.json"), []byte(testCase.ManifestJSON), 0700) err := utils.UpdateAssetsSubpath(testCase.Subpath) require.NoError(t, err) @@ -97,6 +110,9 @@ func TestUpdateAssetsSubpath(t *testing.T) { require.NoError(t, err) require.Equal(t, testCase.ExpectedMainCSS, string(contents)) + contents, err = ioutil.ReadFile(filepath.Join(tempDir, model.CLIENT_DIR, "manifest.json")) + require.NoError(t, err) + require.Equal(t, testCase.ExpectedManifestJSON, string(contents)) }) } }) @@ -190,3 +206,207 @@ const newSubpathRootHtml = ` Mattermost

Cannot connect to Mattermost


We're having trouble connecting to Mattermost. If refreshing this page (Ctrl+R or Command+R) does not work, please verify that your computer is connected to the internet.


` + +const baseManifestJson = `{ + "icons": [ + { + "src": "/static/icon_96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/static/icon_32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/static/icon_16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/static/icon_76x76.png", + "sizes": "76x76", + "type": "image/png" + }, + { + "src": "/static/icon_72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/static/icon_60x60.png", + "sizes": "60x60", + "type": "image/png" + }, + { + "src": "/static/icon_57x57.png", + "sizes": "57x57", + "type": "image/png" + }, + { + "src": "/static/icon_152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/static/icon_144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/static/icon_120x120.png", + "sizes": "120x120", + "type": "image/png" + }, + { + "src": "/static/icon_192x192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "name": "Mattermost", + "short_name": "Mattermost", + "orientation": "any", + "display": "standalone", + "start_url": ".", + "description": "Mattermost is an open source, self-hosted Slack-alternative", + "background_color": "#ffffff" +} +` + +const subpathManifestJson = `{ + "icons": [ + { + "src": "/subpath/static/icon_96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_76x76.png", + "sizes": "76x76", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_60x60.png", + "sizes": "60x60", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_57x57.png", + "sizes": "57x57", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_120x120.png", + "sizes": "120x120", + "type": "image/png" + }, + { + "src": "/subpath/static/icon_192x192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "name": "Mattermost", + "short_name": "Mattermost", + "orientation": "any", + "display": "standalone", + "start_url": ".", + "description": "Mattermost is an open source, self-hosted Slack-alternative", + "background_color": "#ffffff" +} +` + +const newSubpathManifestJson = `{ + "icons": [ + { + "src": "/nested/subpath/static/icon_96x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_76x76.png", + "sizes": "76x76", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_72x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_60x60.png", + "sizes": "60x60", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_57x57.png", + "sizes": "57x57", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_152x152.png", + "sizes": "152x152", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_144x144.png", + "sizes": "144x144", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_120x120.png", + "sizes": "120x120", + "type": "image/png" + }, + { + "src": "/nested/subpath/static/icon_192x192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "name": "Mattermost", + "short_name": "Mattermost", + "orientation": "any", + "display": "standalone", + "start_url": ".", + "description": "Mattermost is an open source, self-hosted Slack-alternative", + "background_color": "#ffffff" +} +` From 912b140cc5e8b7feef3af2746c6a4ff06fb20b53 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Fri, 29 Jun 2018 04:15:01 -0400 Subject: [PATCH 05/10] remove redundant checks in build.mk (#9015) --- build/release.mk | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/build/release.mk b/build/release.mk index 238343e82d..9a41156008 100644 --- a/build/release.mk +++ b/build/release.mk @@ -3,27 +3,15 @@ dist: | check-style test package build-linux: @echo Build Linux amd64 -ifeq ($(BUILDER_GOOS_GOARCH),"linux_amd64") env GOOS=linux GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -else - env GOOS=linux GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -endif build-osx: @echo Build OSX amd64 -ifeq ($(BUILDER_GOOS_GOARCH),"darwin_amd64") env GOOS=darwin GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -else - env GOOS=darwin GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -endif build-windows: @echo Build Windows amd64 -ifeq ($(BUILDER_GOOS_GOARCH),"windows_amd64") env GOOS=windows GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -else - env GOOS=windows GOARCH=amd64 $(GO) install -i $(GOFLAGS) $(GO_LINKER_FLAGS) ./... -endif build: build-linux build-windows build-osx From e51ae397664b6c2b04bfa263429da550240d160e Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Fri, 29 Jun 2018 09:27:06 +0100 Subject: [PATCH 06/10] Fix a really rare race condition in the migration scheduler. (#9000) --- migrations/scheduler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/scheduler.go b/migrations/scheduler.go index 8a7ac30d01..5778c5cb3b 100644 --- a/migrations/scheduler.go +++ b/migrations/scheduler.go @@ -59,7 +59,7 @@ func (scheduler *Scheduler) ScheduleJob(cfg *model.Config, pendingJobs bool, las if state == MIGRATION_STATE_IN_PROGRESS { // Check the migration job isn't wedged. - if job != nil && job.LastActivityAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS { + if job != nil && job.LastActivityAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS && job.CreateAt < model.GetMillis()-MIGRATION_JOB_WEDGED_TIMEOUT_MILLISECONDS { mlog.Warn("Job appears to be wedged. Rescheduling another instance.", mlog.String("scheduler", scheduler.Name()), mlog.String("wedged_job_id", job.Id), mlog.String("migration_key", key)) if err := scheduler.App.Jobs.SetJobError(job, nil); err != nil { mlog.Error("Worker: Failed to set job error", mlog.String("scheduler", scheduler.Name()), mlog.String("job_id", job.Id), mlog.String("error", err.Error())) From 56ba06c0166f46e9fcfcc4f654a3f7346244b5a9 Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Fri, 29 Jun 2018 08:45:04 -0400 Subject: [PATCH 07/10] MM-10980: Conditionally filteres User json properties. (#9018) --- api4/user.go | 6 +++++- api4/user_test.go | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/api4/user.go b/api4/user.go index 14ab3a0a2c..ac702644d5 100644 --- a/api4/user.go +++ b/api4/user.go @@ -154,7 +154,11 @@ func getUserByUsername(c *Context, w http.ResponseWriter, r *http.Request) { if c.HandleEtag(etag, "Get User", w, r) { return } else { - c.App.SanitizeProfile(user, c.IsSystemAdmin()) + if c.Session.UserId == user.Id { + user.Sanitize(map[string]bool{}) + } else { + c.App.SanitizeProfile(user, c.IsSystemAdmin()) + } w.Header().Set(model.HEADER_ETAG_SERVER, etag) w.Write([]byte(user.ToJson())) return diff --git a/api4/user_test.go b/api4/user_test.go index 96aa55d5f6..ad77c8c4cd 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -411,7 +411,7 @@ func TestGetUserByUsername(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowEmailAddress = false }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.PrivacySettings.ShowFullName = false }) - ruser, resp = Client.GetUserByUsername(user.Username, "") + ruser, resp = Client.GetUserByUsername(th.BasicUser2.Username, "") CheckNoError(t, resp) if ruser.Email != "" { @@ -424,6 +424,12 @@ func TestGetUserByUsername(t *testing.T) { t.Fatal("last name should be blank") } + ruser, resp = Client.GetUserByUsername(th.BasicUser.Username, "") + CheckNoError(t, resp) + if len(ruser.NotifyProps) == 0 { + t.Fatal("notify props should be sent") + } + Client.Logout() _, resp = Client.GetUserByUsername(user.Username, "") CheckUnauthorizedStatus(t, resp) From c371ae4db142d061d7e22d9cb72f3942e4d45faf Mon Sep 17 00:00:00 2001 From: George Goldberg Date: Fri, 29 Jun 2018 16:50:06 +0100 Subject: [PATCH 08/10] 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(-) diff --git a/app/import.go b/app/import.go index df20b2ba79..baf9365675 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 b272902898..e7bc055a48 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) { From 4245797cb23b3f9dc99ff556a5ee22c4e14140bc Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Fri, 29 Jun 2018 17:17:35 -0400 Subject: [PATCH 09/10] 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(-) diff --git a/app/server.go b/app/server.go index d71a884d2e..769690295e 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) From 2362a5b7f7c2b4bf2b35b104e8616d847b33b0be Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Fri, 29 Jun 2018 18:13:13 -0400 Subject: [PATCH 10/10] Ignore | characters in postgres channel search (#9019) --- store/sqlstore/channel_store.go | 2 ++ store/storetest/channel_store.go | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index e062d41d19..f82a008582 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -1617,6 +1617,8 @@ func (s SqlChannelStore) buildFulltextClause(term string) (fulltextClause, fullt // Prepare the FULLTEXT portion of the query. if s.DriverName() == model.DATABASE_DRIVER_POSTGRES { + fulltextTerm = strings.Replace(fulltextTerm, "|", "", -1) + splitTerm := strings.Fields(fulltextTerm) for i, t := range strings.Fields(fulltextTerm) { if i == len(splitTerm)-1 { diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index b033e9c98c..ccf4b1c598 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -2028,6 +2028,19 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { t.Fatal("wrong channel returned") } } + + if result := <-search(o1.TeamId, "town square |"); result.Err != nil { + t.Fatal(result.Err) + } else { + channels := result.Data.(*model.ChannelList) + if len(*channels) != 1 { + t.Fatal("should return 1 channel") + } + + if (*channels)[0].Name != o9.Name { + t.Fatal("wrong channel returned") + } + } }) } }