From d3a285e64d051aa8d5c4c9854597dfbcce107675 Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 19 Jan 2017 09:00:13 -0500 Subject: Migrate functions to app package (#5106) * Refactor and move session logic into app package * Refactor email functions into the app package * Refactor password update into app package * Migrate user functions to app package * Move team functions into app package * Migrate channel functions into app package * Pass SiteURL through to app functions * Update based on feedback --- app/channel.go | 340 ++++++++++++++++++++++++++++++++++++++++++++++++- app/email.go | 236 ++++++++++++++++++++++++++++++++++ app/notification.go | 1 - app/oauth.go | 2 +- app/session.go | 54 +++++++- app/session_test.go | 2 +- app/team.go | 352 ++++++++++++++++++++++++++++++++++++++++++++++++++- app/user.go | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 1331 insertions(+), 15 deletions(-) create mode 100644 app/email.go (limited to 'app') diff --git a/app/channel.go b/app/channel.go index aa84e12be..b8e02a149 100644 --- a/app/channel.go +++ b/app/channel.go @@ -5,6 +5,7 @@ package app import ( "fmt" + "net/http" l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" @@ -190,6 +191,128 @@ func CreateDirectChannel(userId string, otherUserId string) (*model.Channel, *mo } } +func UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) { + if result := <-Srv.Store.Channel().Update(channel); result.Err != nil { + return nil, result.Err + } else { + InvalidateCacheForChannel(channel.Id) + return channel, nil + } +} + +func UpdateChannelMemberRoles(channelId string, userId string, newRoles string) (*model.ChannelMember, *model.AppError) { + var member *model.ChannelMember + var err *model.AppError + if member, err = GetChannelMember(channelId, userId); err != nil { + return nil, err + } + + member.Roles = newRoles + + if result := <-Srv.Store.Channel().UpdateMember(member); result.Err != nil { + return nil, result.Err + } + + InvalidateCacheForUser(userId) + return member, nil +} + +func UpdateChannelMemberNotifyProps(data map[string]string, channelId string, userId string) (*model.ChannelMember, *model.AppError) { + var member *model.ChannelMember + var err *model.AppError + if member, err = GetChannelMember(channelId, userId); err != nil { + return nil, err + } + + // update whichever notify properties have been provided, but don't change the others + if markUnread, exists := data["mark_unread"]; exists { + member.NotifyProps["mark_unread"] = markUnread + } + + if desktop, exists := data["desktop"]; exists { + member.NotifyProps["desktop"] = desktop + } + + if result := <-Srv.Store.Channel().UpdateMember(member); result.Err != nil { + return nil, result.Err + } else { + InvalidateCacheForUser(userId) + return member, nil + } +} + +func DeleteChannel(channel *model.Channel, userId string) *model.AppError { + uc := Srv.Store.User().Get(userId) + scm := Srv.Store.Channel().GetMember(channel.Id, userId) + ihc := Srv.Store.Webhook().GetIncomingByChannel(channel.Id) + ohc := Srv.Store.Webhook().GetOutgoingByChannel(channel.Id) + + if uresult := <-uc; uresult.Err != nil { + return uresult.Err + } else if scmresult := <-scm; scmresult.Err != nil { + return scmresult.Err + } else if ihcresult := <-ihc; ihcresult.Err != nil { + return ihcresult.Err + } else if ohcresult := <-ohc; ohcresult.Err != nil { + return ohcresult.Err + } else { + user := uresult.Data.(*model.User) + incomingHooks := ihcresult.Data.([]*model.IncomingWebhook) + outgoingHooks := ohcresult.Data.([]*model.OutgoingWebhook) + // Don't need to do anything with channel member, just wanted to confirm it exists + + if channel.DeleteAt > 0 { + err := model.NewLocAppError("deleteChannel", "api.channel.delete_channel.deleted.app_error", nil, "") + err.StatusCode = http.StatusBadRequest + return err + } + + if channel.Name == model.DEFAULT_CHANNEL { + err := model.NewLocAppError("deleteChannel", "api.channel.delete_channel.cannot.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") + err.StatusCode = http.StatusBadRequest + return err + } + + T := utils.GetUserTranslations(user.Locale) + + post := &model.Post{ + ChannelId: channel.Id, + Message: fmt.Sprintf(T("api.channel.delete_channel.archived"), user.Username), + Type: model.POST_CHANNEL_DELETED, + UserId: userId, + } + + if _, err := CreatePost(post, channel.TeamId, false); err != nil { + l4g.Error(utils.T("api.channel.delete_channel.failed_post.error"), err) + } + + now := model.GetMillis() + for _, hook := range incomingHooks { + if result := <-Srv.Store.Webhook().DeleteIncoming(hook.Id, now); result.Err != nil { + l4g.Error(utils.T("api.channel.delete_channel.incoming_webhook.error"), hook.Id) + } + } + + for _, hook := range outgoingHooks { + if result := <-Srv.Store.Webhook().DeleteOutgoing(hook.Id, now); result.Err != nil { + l4g.Error(utils.T("api.channel.delete_channel.outgoing_webhook.error"), hook.Id) + } + } + + if dresult := <-Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil { + return dresult.Err + } + InvalidateCacheForChannel(channel.Id) + + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, channel.TeamId, "", "", nil) + message.Add("channel_id", channel.Id) + + Publish(message) + } + + return nil +} + func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelMember, *model.AppError) { if channel.DeleteAt > 0 { return nil, model.NewLocAppError("AddUserToChannel", "api.channel.add_user_to_channel.deleted.app_error", nil, "") @@ -205,7 +328,7 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM if result := <-tmchan; result.Err != nil { return nil, result.Err } else { - teamMember := result.Data.(model.TeamMember) + teamMember := result.Data.(*model.TeamMember) if teamMember.DeleteAt > 0 { return nil, model.NewLocAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.deleted.app_error", nil, "") } @@ -216,8 +339,8 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM return nil, result.Err } } else { - channelMember := result.Data.(model.ChannelMember) - return &channelMember, nil + channelMember := result.Data.(*model.ChannelMember) + return channelMember, nil } newMember := &model.ChannelMember{ @@ -396,6 +519,62 @@ func GetChannelByName(channelName, teamId string) (*model.Channel, *model.AppErr } } +func GetChannelsForUser(teamId string, userId string) (*model.ChannelList, *model.AppError) { + if result := <-Srv.Store.Channel().GetChannels(teamId, userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelList), nil + } +} + +func GetChannelsUserNotIn(teamId string, userId string, offset int, limit int) (*model.ChannelList, *model.AppError) { + if result := <-Srv.Store.Channel().GetMoreChannels(teamId, userId, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelList), nil + } +} + +func GetChannelMember(channelId string, userId string) (*model.ChannelMember, *model.AppError) { + if result := <-Srv.Store.Channel().GetMember(channelId, userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelMember), nil + } +} + +func GetChannelMembersByIds(channelId string, userIds []string) (*model.ChannelMembers, *model.AppError) { + if result := <-Srv.Store.Channel().GetMembersByIds(channelId, userIds); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelMembers), nil + } +} + +func GetChannelMembersForUser(teamId string, userId string) (*model.ChannelMembers, *model.AppError) { + if result := <-Srv.Store.Channel().GetMembersForUser(teamId, userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelMembers), nil + } +} + +func GetChannelMemberCount(channelId string) (int64, *model.AppError) { + if result := <-Srv.Store.Channel().GetMemberCount(channelId, true); result.Err != nil { + return 0, result.Err + } else { + return result.Data.(int64), nil + } +} + +func GetChannelCounts(teamId string, userId string) (*model.ChannelCounts, *model.AppError) { + if result := <-Srv.Store.Channel().GetChannelCounts(teamId, userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelCounts), nil + } +} + func JoinChannel(channel *model.Channel, userId string) *model.AppError { userChan := Srv.Store.User().Get(userId) memberChan := Srv.Store.Channel().GetMember(channel.Id, userId) @@ -421,6 +600,76 @@ func JoinChannel(channel *model.Channel, userId string) *model.AppError { return nil } +func LeaveChannel(channelId string, userId string) *model.AppError { + sc := Srv.Store.Channel().Get(channelId, true) + uc := Srv.Store.User().Get(userId) + ccm := Srv.Store.Channel().GetMemberCount(channelId, false) + + if cresult := <-sc; cresult.Err != nil { + return cresult.Err + } else if uresult := <-uc; uresult.Err != nil { + return cresult.Err + } else if ccmresult := <-ccm; ccmresult.Err != nil { + return ccmresult.Err + } else { + channel := cresult.Data.(*model.Channel) + user := uresult.Data.(*model.User) + membersCount := ccmresult.Data.(int64) + + if channel.Type == model.CHANNEL_DIRECT { + err := model.NewLocAppError("LeaveChannel", "api.channel.leave.direct.app_error", nil, "") + err.StatusCode = http.StatusBadRequest + return err + } + + if channel.Type == model.CHANNEL_PRIVATE && membersCount == 1 { + err := model.NewLocAppError("LeaveChannel", "api.channel.leave.last_member.app_error", nil, "userId="+user.Id) + err.StatusCode = http.StatusBadRequest + return err + } + + if err := RemoveUserFromChannel(userId, userId, channel); err != nil { + return err + } + + go PostUserAddRemoveMessage(userId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username), model.POST_JOIN_LEAVE) + } + + return nil +} + +func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel *model.Channel) *model.AppError { + if channel.DeleteAt > 0 { + err := model.NewLocAppError("RemoveUserFromChannel", "api.channel.remove_user_from_channel.deleted.app_error", nil, "") + err.StatusCode = http.StatusBadRequest + return err + } + + if channel.Name == model.DEFAULT_CHANNEL { + return model.NewLocAppError("RemoveUserFromChannel", "api.channel.remove.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") + } + + if cmresult := <-Srv.Store.Channel().RemoveMember(channel.Id, userIdToRemove); cmresult.Err != nil { + return cmresult.Err + } + + InvalidateCacheForUser(userIdToRemove) + InvalidateCacheForChannel(channel.Id) + + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil) + message.Add("user_id", userIdToRemove) + message.Add("remover_id", removerUserId) + go Publish(message) + + // because the removed user no longer belongs to the channel we need to send a separate websocket event + userMsg := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", "", userIdToRemove, nil) + userMsg.Add("channel_id", channel.Id) + userMsg.Add("remover_id", removerUserId) + go Publish(userMsg) + + return nil +} + func PostUserAddRemoveMessage(userId, channelId, teamId, message, postType string) *model.AppError { post := &model.Post{ ChannelId: channelId, @@ -434,3 +683,88 @@ func PostUserAddRemoveMessage(userId, channelId, teamId, message, postType strin return nil } + +func GetNumberOfChannelsOnTeam(teamId string) (int, *model.AppError) { + // Get total number of channels on current team + if result := <-Srv.Store.Channel().GetTeamChannels(teamId); result.Err != nil { + return 0, result.Err + } else { + return len(*result.Data.(*model.ChannelList)), nil + } +} + +func SetActiveChannel(userId string, channelId string) *model.AppError { + status, err := GetStatus(userId) + if err != nil { + status = &model.Status{userId, model.STATUS_ONLINE, false, model.GetMillis(), channelId} + } else { + status.ActiveChannel = channelId + if !status.Manual { + status.Status = model.STATUS_ONLINE + } + status.LastActivityAt = model.GetMillis() + } + + AddStatusCache(status) + + return nil +} + +func UpdateChannelLastViewedAt(channelIds []string, userId string) *model.AppError { + if result := <-Srv.Store.Channel().UpdateLastViewedAt(channelIds, userId); result.Err != nil { + return result.Err + } + + return nil +} + +func SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError) { + if result := <-Srv.Store.Channel().SearchInTeam(teamId, term); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelList), nil + } +} + +func SearchChannelsUserNotIn(teamId string, userId string, term string) (*model.ChannelList, *model.AppError) { + if result := <-Srv.Store.Channel().SearchMore(userId, teamId, term); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.ChannelList), nil + } +} + +func ViewChannel(view *model.ChannelView, teamId string, userId string, clearPushNotifications bool) *model.AppError { + channelIds := []string{view.ChannelId} + + var pchan store.StoreChannel + if len(view.PrevChannelId) > 0 { + channelIds = append(channelIds, view.PrevChannelId) + + if *utils.Cfg.EmailSettings.SendPushNotifications && clearPushNotifications { + pchan = Srv.Store.User().GetUnreadCountForChannel(userId, view.ChannelId) + } + } + + uchan := Srv.Store.Channel().UpdateLastViewedAt(channelIds, userId) + + if pchan != nil { + if result := <-pchan; result.Err != nil { + return result.Err + } else { + if result.Data.(int64) > 0 { + ClearPushNotification(userId, view.ChannelId) + } + } + } + + if result := <-uchan; result.Err != nil { + return result.Err + } + + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, teamId, "", userId, nil) + message.Add("channel_id", view.ChannelId) + go Publish(message) + + return nil +} diff --git a/app/email.go b/app/email.go new file mode 100644 index 000000000..007a24505 --- /dev/null +++ b/app/email.go @@ -0,0 +1,236 @@ +// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "fmt" + "html/template" + "net/url" + + l4g "github.com/alecthomas/log4go" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +func SendChangeUsernameEmail(oldUsername, newUsername, email, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, T("api.templates.username_change_subject", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) + + bodyPage := utils.NewHTMLTemplate("email_change_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.username_change_body.title") + bodyPage.Html["Info"] = template.HTML(T("api.templates.username_change_body.info", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewUsername": newUsername})) + + if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendChangeUsernameEmail", "api.user.send_email_change_username_and_forget.error", nil, err.Error()) + } + + return nil +} + +func SendEmailChangeVerifyEmail(userId, newUserEmail, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(newUserEmail)) + + subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, T("api.templates.email_change_verify_subject", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) + + bodyPage := utils.NewHTMLTemplate("email_change_verify_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.email_change_verify_body.title") + bodyPage.Props["Info"] = T("api.templates.email_change_verify_body.info", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) + bodyPage.Props["VerifyUrl"] = link + bodyPage.Props["VerifyButton"] = T("api.templates.email_change_verify_body.button") + + if err := utils.SendMail(newUserEmail, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendEmailChangeVerifyEmail", "api.user.send_email_change_verify_email_and_forget.error", nil, err.Error()) + } + + return nil +} + +func SendEmailChangeEmail(oldEmail, newEmail, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, T("api.templates.email_change_subject", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) + + bodyPage := utils.NewHTMLTemplate("email_change_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.email_change_body.title") + bodyPage.Html["Info"] = template.HTML(T("api.templates.email_change_body.info", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewEmail": newEmail})) + + if err := utils.SendMail(oldEmail, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendEmailChangeEmail", "api.user.send_email_change_email_and_forget.error", nil, err.Error()) + } + + return nil +} + +func SendVerifyEmail(userId, userEmail, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(userEmail)) + + url, _ := url.Parse(siteURL) + + subject := T("api.templates.verify_subject", + map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) + + bodyPage := utils.NewHTMLTemplate("verify_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.verify_body.title", map[string]interface{}{"ServerURL": url.Host}) + bodyPage.Props["Info"] = T("api.templates.verify_body.info") + bodyPage.Props["VerifyUrl"] = link + bodyPage.Props["Button"] = T("api.templates.verify_body.button") + + if err := utils.SendMail(userEmail, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendVerifyEmail", "api.user.send_verify_email_and_forget.failed.error", nil, err.Error()) + } + + return nil +} + +func SendSignInChangeEmail(email, method, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + subject := T("api.templates.singin_change_email.subject", + map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) + + bodyPage := utils.NewHTMLTemplate("signin_change_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.signin_change_email.body.title") + bodyPage.Html["Info"] = template.HTML(T("api.templates.singin_change_email.body.info", + map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], "Method": method})) + + if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendSignInChangeEmail", "api.user.send_sign_in_change_email_and_forget.error", nil, err.Error()) + } + + return nil +} + +func SendWelcomeEmail(userId string, email string, verified bool, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + rawUrl, _ := url.Parse(siteURL) + + subject := T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host}) + + bodyPage := utils.NewHTMLTemplate("welcome_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.welcome_body.title", map[string]interface{}{"ServerURL": rawUrl.Host}) + bodyPage.Props["Info"] = T("api.templates.welcome_body.info") + bodyPage.Props["Button"] = T("api.templates.welcome_body.button") + bodyPage.Props["Info2"] = T("api.templates.welcome_body.info2") + bodyPage.Props["Info3"] = T("api.templates.welcome_body.info3") + bodyPage.Props["SiteURL"] = siteURL + + if *utils.Cfg.NativeAppSettings.AppDownloadLink != "" { + bodyPage.Props["AppDownloadInfo"] = T("api.templates.welcome_body.app_download_info") + bodyPage.Props["AppDownloadLink"] = *utils.Cfg.NativeAppSettings.AppDownloadLink + } + + if !verified { + link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(email)) + bodyPage.Props["VerifyUrl"] = link + } + + if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendWelcomeEmail", "api.user.send_welcome_email_and_forget.failed.error", nil, err.Error()) + } + + return nil +} + +func SendPasswordChangeEmail(email, method, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + subject := T("api.templates.password_change_subject", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "SiteName": utils.Cfg.TeamSettings.SiteName}) + + bodyPage := utils.NewHTMLTemplate("password_change_body", locale) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = T("api.templates.password_change_body.title") + bodyPage.Html["Info"] = template.HTML(T("api.templates.password_change_body.info", + map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "TeamURL": siteURL, "Method": method})) + + if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendPasswordChangeEmail", "api.user.send_password_change_email_and_forget.error", nil, err.Error()) + } + + return nil +} + +func SendMfaChangeEmail(email string, activated bool, locale, siteURL string) *model.AppError { + T := utils.GetUserTranslations(locale) + + subject := T("api.templates.mfa_change_subject", + map[string]interface{}{"SiteName": utils.Cfg.TeamSettings.SiteName}) + + bodyPage := utils.NewHTMLTemplate("mfa_change_body", locale) + bodyPage.Props["SiteURL"] = siteURL + + bodyText := "" + if activated { + bodyText = "api.templates.mfa_activated_body.info" + bodyPage.Props["Title"] = T("api.templates.mfa_activated_body.title") + } else { + bodyText = "api.templates.mfa_deactivated_body.info" + bodyPage.Props["Title"] = T("api.templates.mfa_deactivated_body.title") + } + + bodyPage.Html["Info"] = template.HTML(T(bodyText, + map[string]interface{}{"SiteURL": siteURL})) + + if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { + return model.NewLocAppError("SendMfaChangeEmail", "api.user.send_mfa_change_email.error", nil, err.Error()) + } + + return nil +} + +func SendInviteEmails(team *model.Team, senderName string, invites []string, siteURL string) { + for _, invite := range invites { + if len(invite) > 0 { + senderRole := utils.T("api.team.invite_members.member") + + subject := utils.T("api.templates.invite_subject", + map[string]interface{}{"SenderName": senderName, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]}) + + bodyPage := utils.NewHTMLTemplate("invite_body", model.DEFAULT_LOCALE) + bodyPage.Props["SiteURL"] = siteURL + bodyPage.Props["Title"] = utils.T("api.templates.invite_body.title") + bodyPage.Html["Info"] = template.HTML(utils.T("api.templates.invite_body.info", + map[string]interface{}{"SenderStatus": senderRole, "SenderName": senderName, "TeamDisplayName": team.DisplayName})) + bodyPage.Props["Button"] = utils.T("api.templates.invite_body.button") + bodyPage.Html["ExtraInfo"] = template.HTML(utils.T("api.templates.invite_body.extra_info", + map[string]interface{}{"TeamDisplayName": team.DisplayName, "TeamURL": siteURL + "/" + team.Name})) + + props := make(map[string]string) + props["email"] = invite + props["id"] = team.Id + props["display_name"] = team.DisplayName + props["name"] = team.Name + props["time"] = fmt.Sprintf("%v", model.GetMillis()) + data := model.MapToJson(props) + hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) + bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_user_complete/?d=%s&h=%s", siteURL, url.QueryEscape(data), url.QueryEscape(hash)) + + if !utils.Cfg.EmailSettings.SendEmailNotifications { + l4g.Info(utils.T("api.team.invite_members.sending.info"), invite, bodyPage.Props["Link"]) + } + + if err := utils.SendMail(invite, subject, bodyPage.Render()); err != nil { + l4g.Error(utils.T("api.team.invite_members.send.error"), err) + } + } + } +} diff --git a/app/notification.go b/app/notification.go index fc1d44f06..060810017 100644 --- a/app/notification.go +++ b/app/notification.go @@ -542,7 +542,6 @@ func ClearPushNotification(userId string, channelId string) *model.AppError { } func sendToPushProxy(msg model.PushNotification) *model.AppError { - msg.ServerId = utils.CfgDiagnosticId tr := &http.Transport{ diff --git a/app/oauth.go b/app/oauth.go index 862897b24..3e8b0b8d2 100644 --- a/app/oauth.go +++ b/app/oauth.go @@ -27,7 +27,7 @@ func RevokeAccessToken(token string) *model.AppError { } if session != nil { - RemoveAllSessionsForUserId(session.UserId) + ClearSessionCacheForUser(session.UserId) } return nil diff --git a/app/session.go b/app/session.go index 3bb167891..289bb6a2d 100644 --- a/app/session.go +++ b/app/session.go @@ -14,6 +14,18 @@ import ( var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE) +func CreateSession(session *model.Session) (*model.Session, *model.AppError) { + if result := <-Srv.Store.Session().Save(session); result.Err != nil { + return nil, result.Err + } else { + session := result.Data.(*model.Session) + + AddSessionToCache(session) + + return session, nil + } +} + func GetSession(token string) (*model.Session, *model.AppError) { metrics := einterfaces.GetMetricsInterface() @@ -51,16 +63,48 @@ func GetSession(token string) (*model.Session, *model.AppError) { return session, nil } -func RemoveAllSessionsForUserId(userId string) { +func GetSessions(userId string) ([]*model.Session, *model.AppError) { + if result := <-Srv.Store.Session().GetSessions(userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Session), nil + } +} + +func RevokeAllSessions(userId string) *model.AppError { + if result := <-Srv.Store.Session().GetSessions(userId); result.Err != nil { + return result.Err + } else { + sessions := result.Data.([]*model.Session) + + for _, session := range sessions { + if session.IsOAuth { + RevokeAccessToken(session.Token) + } else { + if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { + return result.Err + } + } + + RevokeWebrtcToken(session.Id) + } + } + + ClearSessionCacheForUser(userId) + + return nil +} + +func ClearSessionCacheForUser(userId string) { - RemoveAllSessionsForUserIdSkipClusterSend(userId) + ClearSessionCacheForUserSkipClusterSend(userId) if einterfaces.GetClusterInterface() != nil { - einterfaces.GetClusterInterface().RemoveAllSessionsForUserId(userId) + einterfaces.GetClusterInterface().ClearSessionCacheForUser(userId) } } -func RemoveAllSessionsForUserIdSkipClusterSend(userId string) { +func ClearSessionCacheForUserSkipClusterSend(userId string) { keys := sessionCache.Keys() for _, key := range keys { @@ -132,7 +176,7 @@ func RevokeSession(session *model.Session) *model.AppError { } RevokeWebrtcToken(session.Id) - RemoveAllSessionsForUserId(session.UserId) + ClearSessionCacheForUser(session.UserId) return nil } diff --git a/app/session_test.go b/app/session_test.go index 352395c76..aea31cf86 100644 --- a/app/session_test.go +++ b/app/session_test.go @@ -22,7 +22,7 @@ func TestCache(t *testing.T) { t.Fatal("should have items") } - RemoveAllSessionsForUserId(session.UserId) + ClearSessionCacheForUser(session.UserId) rkeys := sessionCache.Keys() if len(rkeys) != len(keys)-1 { diff --git a/app/team.go b/app/team.go index 495e0773f..28d667268 100644 --- a/app/team.go +++ b/app/team.go @@ -4,6 +4,11 @@ package app import ( + "fmt" + "net/http" + "strconv" + "strings" + l4g "github.com/alecthomas/log4go" "github.com/mattermost/platform/model" @@ -24,6 +29,57 @@ func CreateTeam(team *model.Team) (*model.Team, *model.AppError) { } } +func UpdateTeam(team *model.Team) (*model.Team, *model.AppError) { + var oldTeam *model.Team + var err *model.AppError + if oldTeam, err = GetTeam(team.Id); err != nil { + return nil, err + } + + oldTeam.DisplayName = team.DisplayName + oldTeam.Description = team.Description + oldTeam.InviteId = team.InviteId + oldTeam.AllowOpenInvite = team.AllowOpenInvite + oldTeam.CompanyName = team.CompanyName + oldTeam.AllowedDomains = team.AllowedDomains + + if result := <-Srv.Store.Team().Update(oldTeam); result.Err != nil { + return nil, result.Err + } + + return oldTeam, nil +} + +func UpdateTeamMemberRoles(teamId string, userId string, newRoles string) (*model.TeamMember, *model.AppError) { + var member *model.TeamMember + if result := <-Srv.Store.Team().GetTeamsForUser(userId); result.Err != nil { + return nil, result.Err + } else { + members := result.Data.([]*model.TeamMember) + for _, m := range members { + if m.TeamId == teamId { + member = m + } + } + } + + if member == nil { + err := model.NewLocAppError("UpdateTeamMemberRoles", "api.team.update_member_roles.not_a_member", nil, "userId="+userId+" teamId="+teamId) + err.StatusCode = http.StatusBadRequest + return nil, err + } + + member.Roles = newRoles + + if result := <-Srv.Store.Team().UpdateMember(member); result.Err != nil { + return nil, result.Err + } + + ClearSessionCacheForUser(userId) + + return member, nil +} + func JoinUserToTeamById(teamId string, user *model.User) *model.AppError { if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { return result.Err @@ -32,6 +88,55 @@ func JoinUserToTeamById(teamId string, user *model.User) *model.AppError { } } +func JoinUserToTeamByHash(userId string, hash string, data string) (*model.Team, *model.AppError) { + props := model.MapFromJson(strings.NewReader(data)) + + if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { + return nil, model.NewLocAppError("JoinUserToTeamByHash", "api.user.create_user.signup_link_invalid.app_error", nil, "") + } + + t, timeErr := strconv.ParseInt(props["time"], 10, 64) + if timeErr != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours + return nil, model.NewLocAppError("JoinUserToTeamByHash", "api.user.create_user.signup_link_expired.app_error", nil, "") + } + + var team *model.Team + var err *model.AppError + if team, err = GetTeam(props["id"]); err != nil { + return nil, err + } + + var user *model.User + if user, err = GetUser(userId); err != nil { + return nil, err + } + + if err := JoinUserToTeam(team, user); err != nil { + return nil, err + } + + return team, nil +} + +func JoinUserToTeamByInviteId(inviteId string, userId string) (*model.Team, *model.AppError) { + var team *model.Team + var err *model.AppError + if team, err = GetTeamByInviteId(inviteId); err != nil { + return nil, err + } + + var user *model.User + if user, err = GetUser(userId); err != nil { + return nil, err + } + + if err := JoinUserToTeam(team, user); err != nil { + return nil, err + } + + return team, nil +} + func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError { tm := &model.TeamMember{ @@ -49,7 +154,7 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError { if etmr := <-Srv.Store.Team().GetMember(team.Id, user.Id); etmr.Err == nil { // Membership alredy exists. Check if deleted and and update, otherwise do nothing - rtm := etmr.Data.(model.TeamMember) + rtm := etmr.Data.(*model.TeamMember) // Do nothing if already added if rtm.DeleteAt == 0 { @@ -75,12 +180,52 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError { l4g.Error(utils.T("api.user.create_user.joining.error"), user.Id, team.Id, err) } - RemoveAllSessionsForUserId(user.Id) + ClearSessionCacheForUser(user.Id) InvalidateCacheForUser(user.Id) return nil } +func GetTeam(teamId string) (*model.Team, *model.AppError) { + if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Team), nil + } +} + +func GetTeamByName(name string) (*model.Team, *model.AppError) { + if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Team), nil + } +} + +func GetTeamByInviteId(inviteId string) (*model.Team, *model.AppError) { + if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.Team), nil + } +} + +func GetAllTeams() ([]*model.Team, *model.AppError) { + if result := <-Srv.Store.Team().GetAll(); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Team), nil + } +} + +func GetAllOpenTeams() ([]*model.Team, *model.AppError) { + if result := <-Srv.Store.Team().GetAllTeamListing(); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.Team), nil + } +} + func GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) { if result := <-Srv.Store.Team().GetTeamsByUserId(userId); result.Err != nil { return nil, result.Err @@ -88,3 +233,206 @@ func GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) { return result.Data.([]*model.Team), nil } } + +func GetTeamMember(teamId, userId string) (*model.TeamMember, *model.AppError) { + if result := <-Srv.Store.Team().GetMember(teamId, userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(*model.TeamMember), nil + } +} + +func GetTeamMembersForUser(userId string) ([]*model.TeamMember, *model.AppError) { + if result := <-Srv.Store.Team().GetTeamsForUser(userId); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.TeamMember), nil + } +} + +func GetTeamMembers(teamId string, offset int, limit int) ([]*model.TeamMember, *model.AppError) { + if result := <-Srv.Store.Team().GetMembers(teamId, offset, limit); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.TeamMember), nil + } +} + +func GetTeamMembersByIds(teamId string, userIds []string) ([]*model.TeamMember, *model.AppError) { + if result := <-Srv.Store.Team().GetMembersByIds(teamId, userIds); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.TeamMember), nil + } +} + +func LeaveTeam(team *model.Team, user *model.User) *model.AppError { + var teamMember *model.TeamMember + var err *model.AppError + + if teamMember, err = GetTeamMember(team.Id, user.Id); err != nil { + return model.NewLocAppError("LeaveTeam", "api.team.remove_user_from_team.missing.app_error", nil, err.Error()) + } + + var channelList *model.ChannelList + + if result := <-Srv.Store.Channel().GetChannels(team.Id, user.Id); result.Err != nil { + if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" { + channelList = &model.ChannelList{} + } else { + return result.Err + } + + } else { + channelList = result.Data.(*model.ChannelList) + } + + for _, channel := range *channelList { + if channel.Type != model.CHANNEL_DIRECT { + InvalidateCacheForChannel(channel.Id) + if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil { + return result.Err + } + } + } + + // Send the websocket message before we actually do the remove so the user being removed gets it. + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LEAVE_TEAM, team.Id, "", "", nil) + message.Add("user_id", user.Id) + message.Add("team_id", team.Id) + Publish(message) + + teamMember.Roles = "" + teamMember.DeleteAt = model.GetMillis() + + if result := <-Srv.Store.Team().UpdateMember(teamMember); result.Err != nil { + return result.Err + } + + if uua := <-Srv.Store.User().UpdateUpdateAt(user.Id); uua.Err != nil { + return uua.Err + } + + // delete the preferences that set the last channel used in the team and other team specific preferences + if result := <-Srv.Store.Preference().DeleteCategory(user.Id, team.Id); result.Err != nil { + return result.Err + } + + ClearSessionCacheForUser(user.Id) + InvalidateCacheForUser(user.Id) + + return nil +} + +func InviteNewUsersToTeam(emailList []string, teamId, senderId, siteURL string) *model.AppError { + tchan := Srv.Store.Team().Get(teamId) + uchan := Srv.Store.User().Get(senderId) + + var team *model.Team + if result := <-tchan; result.Err != nil { + return result.Err + } else { + team = result.Data.(*model.Team) + } + + var user *model.User + if result := <-uchan; result.Err != nil { + return result.Err + } else { + user = result.Data.(*model.User) + } + + SendInviteEmails(team, user.GetDisplayName(), emailList, siteURL) + + return nil +} + +func FindTeamByName(name string) bool { + if result := <-Srv.Store.Team().GetByName(name); result.Err != nil { + return false + } else { + return true + } +} + +func GetTeamsUnreadForUser(teamId string, userId string) ([]*model.TeamUnread, *model.AppError) { + if result := <-Srv.Store.Team().GetTeamsUnreadForUser(teamId, userId); result.Err != nil { + return nil, result.Err + } else { + data := result.Data.([]*model.ChannelUnread) + var members []*model.TeamUnread + membersMap := make(map[string]*model.TeamUnread) + + unreads := func(cu *model.ChannelUnread, tu *model.TeamUnread) *model.TeamUnread { + tu.MentionCount += cu.MentionCount + + if cu.NotifyProps["mark_unread"] != model.CHANNEL_MARK_UNREAD_MENTION { + tu.MsgCount += (cu.TotalMsgCount - cu.MsgCount) + } + + return tu + } + + for i := range data { + id := data[i].TeamId + if mu, ok := membersMap[id]; ok { + membersMap[id] = unreads(data[i], mu) + } else { + membersMap[id] = unreads(data[i], &model.TeamUnread{ + MsgCount: 0, + MentionCount: 0, + TeamId: id, + }) + } + } + + for _, val := range membersMap { + members = append(members, val) + } + + return members, nil + } +} + +func PermanentDeleteTeam(team *model.Team) *model.AppError { + team.DeleteAt = model.GetMillis() + if result := <-Srv.Store.Team().Update(team); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Channel().PermanentDeleteByTeam(team.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Team().RemoveAllMembersByTeam(team.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Team().PermanentDelete(team.Id); result.Err != nil { + return result.Err + } + + return nil +} + +func GetTeamStats(teamId string) (*model.TeamStats, *model.AppError) { + tchan := Srv.Store.Team().GetTotalMemberCount(teamId) + achan := Srv.Store.Team().GetActiveMemberCount(teamId) + + stats := &model.TeamStats{} + stats.TeamId = teamId + + if result := <-tchan; result.Err != nil { + return nil, result.Err + } else { + stats.TotalMemberCount = result.Data.(int64) + } + + if result := <-achan; result.Err != nil { + return nil, result.Err + } else { + stats.ActiveMemberCount = result.Data.(int64) + } + + return stats, nil +} diff --git a/app/user.go b/app/user.go index 909c8cca9..8324417e8 100644 --- a/app/user.go +++ b/app/user.go @@ -15,11 +15,13 @@ import ( "image/png" "io" "io/ioutil" + "mime/multipart" "net/http" "strconv" "strings" l4g "github.com/alecthomas/log4go" + "github.com/disintegration/imaging" "github.com/golang/freetype" "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" @@ -132,8 +134,8 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) { ruser := result.Data.(*model.User) if user.EmailVerified { - if cresult := <-Srv.Store.User().VerifyEmail(ruser.Id); cresult.Err != nil { - l4g.Error(utils.T("api.user.create_user.verified.error"), cresult.Err) + if err := VerifyUserEmail(ruser.Id); err != nil { + l4g.Error(utils.T("api.user.create_user.verified.error"), err) } } @@ -334,6 +336,14 @@ func GetUsersNotInChannel(teamId string, channelId string, offset int, limit int } } +func GetUsersByIds(userIds []string) (map[string]*model.User, *model.AppError) { + if result := <-Srv.Store.User().GetProfileByIds(userIds, true); result.Err != nil { + return nil, result.Err + } else { + return result.Data.(map[string]*model.User), nil + } +} + func ActivateMfa(userId, token string) *model.AppError { mfaInterface := einterfaces.GetMfaInterface() if mfaInterface == nil { @@ -479,3 +489,348 @@ func GetProfileImage(user *model.User) ([]byte, *model.AppError) { return img, nil } + +func SetProfileImage(userId string, imageData *multipart.FileHeader) *model.AppError { + file, err := imageData.Open() + defer file.Close() + if err != nil { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.open.app_error", nil, err.Error()) + } + + // Decode image config first to check dimensions before loading the whole thing into memory later on + config, _, err := image.DecodeConfig(file) + if err != nil { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.decode_config.app_error", nil, err.Error()) + } else if config.Width*config.Height > model.MaxImageSize { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.too_large.app_error", nil, err.Error()) + } + + file.Seek(0, 0) + + // Decode image into Image object + img, _, err := image.Decode(file) + if err != nil { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error()) + } + + // Scale profile image + img = imaging.Resize(img, utils.Cfg.FileSettings.ProfileWidth, utils.Cfg.FileSettings.ProfileHeight, imaging.Lanczos) + + buf := new(bytes.Buffer) + err = png.Encode(buf, img) + if err != nil { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error()) + } + + path := "users/" + userId + "/profile.png" + + if err := WriteFile(buf.Bytes(), path); err != nil { + return model.NewLocAppError("SetProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "") + } + + Srv.Store.User().UpdateLastPictureUpdate(userId) + + if user, err := GetUser(userId); err != nil { + l4g.Error(utils.T("api.user.get_me.getting.error"), userId) + } else { + options := utils.Cfg.GetSanitizeOptions() + user.SanitizeProfile(options) + + omitUsers := make(map[string]bool, 1) + omitUsers[userId] = true + message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", omitUsers) + message.Add("user", user) + + Publish(message) + } + + return nil +} + +func UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) { + if active { + user.DeleteAt = 0 + } else { + user.DeleteAt = model.GetMillis() + } + + if result := <-Srv.Store.User().Update(user, true); result.Err != nil { + return nil, result.Err + } else { + if user.DeleteAt > 0 { + if err := RevokeAllSessions(user.Id); err != nil { + return nil, err + } + } + + if extra := <-Srv.Store.Channel().ExtraUpdateByUser(user.Id, model.GetMillis()); extra.Err != nil { + return nil, extra.Err + } + + ruser := result.Data.([2]*model.User)[0] + options := utils.Cfg.GetSanitizeOptions() + options["passwordupdate"] = false + ruser.Sanitize(options) + + if !active { + SetStatusOffline(ruser.Id, false) + } + + return ruser, nil + } +} + +func UpdateUser(user *model.User, siteURL string) (*model.User, *model.AppError) { + if result := <-Srv.Store.User().Update(user, false); result.Err != nil { + return nil, result.Err + } else { + rusers := result.Data.([2]*model.User) + + if rusers[0].Email != rusers[1].Email { + go func() { + if err := SendEmailChangeEmail(rusers[1].Email, rusers[0].Email, rusers[0].Locale, siteURL); err != nil { + l4g.Error(err.Error()) + } + }() + + if utils.Cfg.EmailSettings.RequireEmailVerification { + go func() { + if err := SendEmailChangeVerifyEmail(rusers[0].Id, rusers[0].Email, rusers[0].Locale, siteURL); err != nil { + l4g.Error(err.Error()) + } + }() + } + } + + if rusers[0].Username != rusers[1].Username { + go func() { + if err := SendChangeUsernameEmail(rusers[1].Username, rusers[0].Username, rusers[0].Email, rusers[0].Locale, siteURL); err != nil { + l4g.Error(err.Error()) + } + }() + } + + InvalidateCacheForUser(user.Id) + + return rusers[0], nil + } +} + +func UpdatePassword(user *model.User, hashedPassword string) *model.AppError { + if result := <-Srv.Store.User().UpdatePassword(user.Id, hashedPassword); result.Err != nil { + return model.NewLocAppError("UpdatePassword", "api.user.update_password.failed.app_error", nil, result.Err.Error()) + } + + return nil +} + +func UpdatePasswordSendEmail(user *model.User, hashedPassword, method, siteURL string) *model.AppError { + if err := UpdatePassword(user, hashedPassword); err != nil { + return err + } + + go func() { + if err := SendPasswordChangeEmail(user.Email, method, user.Locale, siteURL); err != nil { + l4g.Error(err.Error()) + } + }() + + return nil +} + +func CreatePasswordRecovery(userId string) (*model.PasswordRecovery, *model.AppError) { + recovery := &model.PasswordRecovery{} + recovery.UserId = userId + + if result := <-Srv.Store.PasswordRecovery().SaveOrUpdate(recovery); result.Err != nil { + return nil, result.Err + } + + return recovery, nil +} + +func GetPasswordRecovery(code string) (*model.PasswordRecovery, *model.AppError) { + if result := <-Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil { + return nil, model.NewLocAppError("GetPasswordRecovery", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error()) + } else { + return result.Data.(*model.PasswordRecovery), nil + } +} + +func DeletePasswordRecoveryForUser(userId string) *model.AppError { + if result := <-Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil { + return result.Err + } + + return nil +} + +func UpdateUserRoles(userId string, newRoles string) (*model.User, *model.AppError) { + var user *model.User + var err *model.AppError + if user, err = GetUser(userId); err != nil { + err.StatusCode = http.StatusBadRequest + return nil, err + } + + user.Roles = newRoles + uchan := Srv.Store.User().Update(user, true) + schan := Srv.Store.Session().UpdateRoles(user.Id, newRoles) + + var ruser *model.User + if result := <-uchan; result.Err != nil { + return nil, result.Err + } else { + ruser = result.Data.([2]*model.User)[0] + } + + if result := <-schan; result.Err != nil { + // soft error since the user roles were still updated + l4g.Error(result.Err) + } + + ClearSessionCacheForUser(user.Id) + + return ruser, nil +} + +func PermanentDeleteUser(user *model.User) *model.AppError { + l4g.Warn(utils.T("api.user.permanent_delete_user.attempting.warn"), user.Email, user.Id) + if user.IsInRole(model.ROLE_SYSTEM_ADMIN.Id) { + l4g.Warn(utils.T("api.user.permanent_delete_user.system_admin.warn"), user.Email) + } + + if _, err := UpdateActive(user, false); err != nil { + return err + } + + if result := <-Srv.Store.Session().PermanentDeleteSessionsByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.OAuth().PermanentDeleteAuthDataByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Webhook().PermanentDeleteIncomingByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Webhook().PermanentDeleteOutgoingByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Channel().PermanentDeleteMembersByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Post().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.User().PermanentDelete(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Audit().PermanentDeleteByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.Team().RemoveAllMembersByUser(user.Id); result.Err != nil { + return result.Err + } + + if result := <-Srv.Store.PasswordRecovery().Delete(user.Id); result.Err != nil { + return result.Err + } + + l4g.Warn(utils.T("api.user.permanent_delete_user.deleted.warn"), user.Email, user.Id) + + return nil +} + +func PermanentDeleteAllUsers() *model.AppError { + if result := <-Srv.Store.User().GetAll(); result.Err != nil { + return result.Err + } else { + users := result.Data.([]*model.User) + for _, user := range users { + PermanentDeleteUser(user) + } + } + + return nil +} + +func VerifyUserEmail(userId string) *model.AppError { + if err := (<-Srv.Store.User().VerifyEmail(userId)).Err; err != nil { + return err + } + + return nil +} + +func SearchUsersInChannel(channelId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) { + if result := <-Srv.Store.User().SearchInChannel(channelId, term, searchOptions); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.User), nil + } +} + +func SearchUsersNotInChannel(teamId string, channelId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) { + if result := <-Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.User), nil + } +} + +func SearchUsersInTeam(teamId string, term string, searchOptions map[string]bool) ([]*model.User, *model.AppError) { + if result := <-Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil { + return nil, result.Err + } else { + return result.Data.([]*model.User), nil + } +} + +func AutocompleteUsersInChannel(teamId string, channelId string, term string, searchOptions map[string]bool) (*model.UserAutocompleteInChannel, *model.AppError) { + uchan := Srv.Store.User().SearchInChannel(channelId, term, searchOptions) + nuchan := Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions) + + autocomplete := &model.UserAutocompleteInChannel{} + + if result := <-uchan; result.Err != nil { + return nil, result.Err + } else { + autocomplete.InChannel = result.Data.([]*model.User) + } + + if result := <-nuchan; result.Err != nil { + return nil, result.Err + } else { + autocomplete.OutOfChannel = result.Data.([]*model.User) + } + + return autocomplete, nil +} + +func AutocompleteUsersInTeam(teamId string, term string, searchOptions map[string]bool) (*model.UserAutocompleteInTeam, *model.AppError) { + autocomplete := &model.UserAutocompleteInTeam{} + + if result := <-Srv.Store.User().Search(teamId, term, searchOptions); result.Err != nil { + return nil, result.Err + } else { + autocomplete.InTeam = result.Data.([]*model.User) + } + + return autocomplete, nil +} -- cgit v1.2.3-1-g7c22