diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-01-19 09:00:13 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-19 09:00:13 -0500 |
commit | d3a285e64d051aa8d5c4c9854597dfbcce107675 (patch) | |
tree | e16ca3d52b6347a754e506aa8cac7457c62d3639 | |
parent | 61b7226533568f3261fc233538ce998bb71a5345 (diff) | |
download | chat-d3a285e64d051aa8d5c4c9854597dfbcce107675.tar.gz chat-d3a285e64d051aa8d5c4c9854597dfbcce107675.tar.bz2 chat-d3a285e64d051aa8d5c4c9854597dfbcce107675.zip |
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
34 files changed, 1931 insertions, 1657 deletions
diff --git a/api/apitestlib.go b/api/apitestlib.go index abd4767b7..09aed0e33 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -99,7 +99,7 @@ func (me *TestHelper) InitSystemAdmin() *TestHelper { me.SystemAdminUser = me.CreateUser(me.SystemAdminClient) LinkUserToTeam(me.SystemAdminUser, me.SystemAdminTeam) me.SystemAdminClient.SetTeamId(me.SystemAdminTeam.Id) - UpdateUserRoles(me.SystemAdminUser, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_ADMIN.Id) + app.UpdateUserRoles(me.SystemAdminUser.Id, model.ROLE_SYSTEM_USER.Id+" "+model.ROLE_SYSTEM_ADMIN.Id) me.SystemAdminUser.Password = "Password1" me.LoginSystemAdmin() me.SystemAdminChannel = me.CreateChannel(me.SystemAdminClient, me.SystemAdminTeam) @@ -180,9 +180,9 @@ func MakeUserChannelAdmin(user *model.User, channel *model.Channel) { utils.DisableDebugLogForTest() if cmr := <-app.Srv.Store.Channel().GetMember(channel.Id, user.Id); cmr.Err == nil { - cm := cmr.Data.(model.ChannelMember) + cm := cmr.Data.(*model.ChannelMember) cm.Roles = "channel_admin channel_user" - if sr := <-app.Srv.Store.Channel().UpdateMember(&cm); sr.Err != nil { + if sr := <-app.Srv.Store.Channel().UpdateMember(cm); sr.Err != nil { utils.EnableDebugLogForTest() panic(sr.Err) } @@ -198,9 +198,9 @@ func MakeUserChannelUser(user *model.User, channel *model.Channel) { utils.DisableDebugLogForTest() if cmr := <-app.Srv.Store.Channel().GetMember(channel.Id, user.Id); cmr.Err == nil { - cm := cmr.Data.(model.ChannelMember) + cm := cmr.Data.(*model.ChannelMember) cm.Roles = "channel_user" - if sr := <-app.Srv.Store.Channel().UpdateMember(&cm); sr.Err != nil { + if sr := <-app.Srv.Store.Channel().UpdateMember(cm); sr.Err != nil { utils.EnableDebugLogForTest() panic(sr.Err) } diff --git a/api/channel.go b/api/channel.go index b8b73bf38..4c0c56496 100644 --- a/api/channel.go +++ b/api/channel.go @@ -13,7 +13,6 @@ import ( "github.com/gorilla/mux" "github.com/mattermost/platform/app" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -83,12 +82,11 @@ func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { if channel.TeamId == c.TeamId { // Get total number of channels on current team - if result := <-app.Srv.Store.Channel().GetTeamChannels(channel.TeamId); result.Err != nil { - c.Err = model.NewLocAppError("createChannel", "api.channel.get_channels.error", nil, result.Err.Message) + if count, err := app.GetNumberOfChannelsOnTeam(channel.TeamId); err != nil { + c.Err = model.NewLocAppError("createChannel", "api.channel.get_channels.error", nil, err.Error()) return } else { - data := result.Data.(*model.ChannelList) - if int64(len(*data)+1) > *utils.Cfg.TeamSettings.MaxChannelsPerTeam { + if int64(count+1) > *utils.Cfg.TeamSettings.MaxChannelsPerTeam { c.Err = model.NewLocAppError("createChannel", "api.channel.create_channel.max_channel_limit.app_error", map[string]interface{}{"MaxChannelsPerTeam": *utils.Cfg.TeamSettings.MaxChannelsPerTeam}, "") return } @@ -148,68 +146,66 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - sc := app.Srv.Store.Channel().Get(channel.Id, true) - cmc := app.Srv.Store.Channel().GetMember(channel.Id, c.Session.UserId) + var oldChannel *model.Channel + var err *model.AppError + if oldChannel, err = app.GetChannel(channel.Id); err != nil { + c.Err = err + return + } - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err + if _, err = app.GetChannelMember(channel.Id, c.Session.UserId); err != nil { + c.Err = err return - } else if cmcresult := <-cmc; cmcresult.Err != nil { - c.Err = cmcresult.Err + } + + if !CanManageChannel(c, channel) { return - } else { - oldChannel := cresult.Data.(*model.Channel) - // Don't need to do anything with channel member, just wanted to confirm it exists + } - if !CanManageChannel(c, channel) { - return - } + if oldChannel.DeleteAt > 0 { + c.Err = model.NewLocAppError("updateChannel", "api.channel.update_channel.deleted.app_error", nil, "") + c.Err.StatusCode = http.StatusBadRequest + return + } - if oldChannel.DeleteAt > 0 { - c.Err = model.NewLocAppError("updateChannel", "api.channel.update_channel.deleted.app_error", nil, "") + if oldChannel.Name == model.DEFAULT_CHANNEL { + if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) { + c.Err = model.NewLocAppError("updateChannel", "api.channel.update_channel.tried.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") c.Err.StatusCode = http.StatusBadRequest return } + } - if oldChannel.Name == model.DEFAULT_CHANNEL { - if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) { - c.Err = model.NewLocAppError("updateChannel", "api.channel.update_channel.tried.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - } - - oldChannel.Header = channel.Header - oldChannel.Purpose = channel.Purpose + oldChannel.Header = channel.Header + oldChannel.Purpose = channel.Purpose - oldChannelDisplayName := oldChannel.DisplayName + oldChannelDisplayName := oldChannel.DisplayName - if len(channel.DisplayName) > 0 { - oldChannel.DisplayName = channel.DisplayName - } + if len(channel.DisplayName) > 0 { + oldChannel.DisplayName = channel.DisplayName + } - if len(channel.Name) > 0 { - oldChannel.Name = channel.Name - } + if len(channel.Name) > 0 { + oldChannel.Name = channel.Name + } - if len(channel.Type) > 0 { - oldChannel.Type = channel.Type - } + if len(channel.Type) > 0 { + oldChannel.Type = channel.Type + } - app.InvalidateCacheForChannel(oldChannel.Id) - if ucresult := <-app.Srv.Store.Channel().Update(oldChannel); ucresult.Err != nil { - c.Err = ucresult.Err - return - } else { - if oldChannelDisplayName != channel.DisplayName { - if err := app.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelDisplayName, channel.DisplayName); err != nil { - l4g.Error(err.Error()) - } + if _, err := app.UpdateChannel(oldChannel); err != nil { + c.Err = err + return + } else { + if oldChannelDisplayName != channel.DisplayName { + if err := app.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelDisplayName, channel.DisplayName); err != nil { + l4g.Error(err.Error()) } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(oldChannel.ToJson())) } + c.LogAudit("name=" + channel.Name) + w.Write([]byte(oldChannel.ToJson())) } + } func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { @@ -227,37 +223,34 @@ func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) { return } - sc := app.Srv.Store.Channel().Get(channelId, true) - cmc := app.Srv.Store.Channel().GetMember(channelId, c.Session.UserId) - - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(channelId); err != nil { + c.Err = err return - } else if cmcresult := <-cmc; cmcresult.Err != nil { - c.Err = cmcresult.Err + } + + if _, err = app.GetChannelMember(channelId, c.Session.UserId); err != nil { + c.Err = err return - } else { - channel := cresult.Data.(*model.Channel) - // Don't need to do anything with channel member, just wanted to confirm it exists + } - if !CanManageChannel(c, channel) { - return - } + if !CanManageChannel(c, channel) { + return + } - oldChannelHeader := channel.Header - channel.Header = channelHeader + oldChannelHeader := channel.Header + channel.Header = channelHeader - app.InvalidateCacheForChannel(channel.Id) - if ucresult := <-app.Srv.Store.Channel().Update(channel); ucresult.Err != nil { - c.Err = ucresult.Err - return - } else { - if err := app.PostUpdateChannelHeaderMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelHeader, channelHeader); err != nil { - l4g.Error(err.Error()) - } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(channel.ToJson())) + if _, err := app.UpdateChannel(channel); err != nil { + c.Err = err + return + } else { + if err := app.PostUpdateChannelHeaderMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelHeader, channelHeader); err != nil { + l4g.Error(err.Error()) } + c.LogAudit("name=" + channel.Name) + w.Write([]byte(channel.ToJson())) } } @@ -276,37 +269,34 @@ func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) { return } - sc := app.Srv.Store.Channel().Get(channelId, true) - cmc := app.Srv.Store.Channel().GetMember(channelId, c.Session.UserId) - - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(channelId); err != nil { + c.Err = err return - } else if cmcresult := <-cmc; cmcresult.Err != nil { - c.Err = cmcresult.Err + } + + if _, err = app.GetChannelMember(channelId, c.Session.UserId); err != nil { + c.Err = err return - } else { - channel := cresult.Data.(*model.Channel) - // Don't need to do anything with channel member, just wanted to confirm it exists + } - if !CanManageChannel(c, channel) { - return - } + if !CanManageChannel(c, channel) { + return + } - oldChannelPurpose := channel.Purpose - channel.Purpose = channelPurpose + oldChannelPurpose := channel.Purpose + channel.Purpose = channelPurpose - app.InvalidateCacheForChannel(channel.Id) - if ucresult := <-app.Srv.Store.Channel().Update(channel); ucresult.Err != nil { - c.Err = ucresult.Err - return - } else { - if err := app.PostUpdateChannelPurposeMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelPurpose, channelPurpose); err != nil { - l4g.Error(err.Error()) - } - c.LogAudit("name=" + channel.Name) - w.Write([]byte(channel.ToJson())) + if _, err := app.UpdateChannel(channel); err != nil { + c.Err = err + return + } else { + if err := app.PostUpdateChannelPurposeMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelPurpose, channelPurpose); err != nil { + l4g.Error(err.Error()) } + c.LogAudit("name=" + channel.Name) + w.Write([]byte(channel.ToJson())) } } @@ -319,24 +309,23 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) { // user is already in the team // Get's all channels the user is a member of - if result := <-app.Srv.Store.Channel().GetChannels(c.TeamId, c.Session.UserId); result.Err != nil { - if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" { + if channels, err := app.GetChannelsForUser(c.TeamId, c.Session.UserId); err != nil { + if err.Id == "store.sql_channel.get_channels.not_found.app_error" { // lets make sure the user is valid - if result := <-app.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { - c.Err = result.Err + if _, err := app.GetUser(c.Session.UserId); err != nil { + c.Err = err c.RemoveSessionCookie(w, r) l4g.Error(utils.T("api.channel.get_channels.error"), c.Session.UserId) return } } - c.Err = result.Err + c.Err = err return - } else if HandleEtag(result.Data.(*model.ChannelList).Etag(), "Get Channels", w, r) { + } else if HandleEtag(channels.Etag(), "Get Channels", w, r) { return } else { - data := result.Data.(*model.ChannelList) - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) + w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag()) + w.Write([]byte(channels.ToJson())) } } @@ -360,13 +349,12 @@ func getMoreChannelsPage(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Channel().GetMoreChannels(c.TeamId, c.Session.UserId, offset, limit); result.Err != nil { - c.Err = result.Err + if channels, err := app.GetChannelsUserNotIn(c.TeamId, c.Session.UserId, offset, limit); err != nil { + c.Err = err return } else { - data := result.Data.(*model.ChannelList) - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) + w.Header().Set(model.HEADER_ETAG_SERVER, channels.Etag()) + w.Write([]byte(channels.ToJson())) } } @@ -374,15 +362,14 @@ func getChannelCounts(c *Context, w http.ResponseWriter, r *http.Request) { // user is already in the team - if result := <-app.Srv.Store.Channel().GetChannelCounts(c.TeamId, c.Session.UserId); result.Err != nil { - c.Err = model.NewLocAppError("getChannelCounts", "api.channel.get_channel_counts.app_error", nil, result.Err.Message) + if counts, err := app.GetChannelCounts(c.TeamId, c.Session.UserId); err != nil { + c.Err = model.NewLocAppError("getChannelCounts", "api.channel.get_channel_counts.app_error", nil, err.Message) return - } else if HandleEtag(result.Data.(*model.ChannelCounts).Etag(), "Get Channel Counts", w, r) { + } else if HandleEtag(counts.Etag(), "Get Channel Counts", w, r) { return } else { - data := result.Data.(*model.ChannelCounts) - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) + w.Header().Set(model.HEADER_ETAG_SERVER, counts.Etag()) + w.Write([]byte(counts.ToJson())) } } @@ -427,55 +414,15 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["channel_id"] - sc := app.Srv.Store.Channel().Get(id, true) - uc := app.Srv.Store.User().Get(c.Session.UserId) - ccm := app.Srv.Store.Channel().GetMemberCount(id, false) - - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err - return - } else if uresult := <-uc; uresult.Err != nil { - c.Err = cresult.Err - return - } else if ccmresult := <-ccm; ccmresult.Err != nil { - c.Err = ccmresult.Err + err := app.LeaveChannel(id, c.Session.UserId) + if err != nil { + c.Err = err return - } else { - channel := cresult.Data.(*model.Channel) - user := uresult.Data.(*model.User) - membersCount := ccmresult.Data.(int64) - - if channel.Type == model.CHANNEL_DIRECT { - c.Err = model.NewLocAppError("leave", "api.channel.leave.direct.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if channel.Type == model.CHANNEL_PRIVATE && membersCount == 1 { - c.Err = model.NewLocAppError("leave", "api.channel.leave.last_member.app_error", nil, "userId="+user.Id) - c.Err.StatusCode = http.StatusBadRequest - return - } - - if channel.Name == model.DEFAULT_CHANNEL { - c.Err = model.NewLocAppError("leave", "api.channel.leave.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if cmresult := <-app.Srv.Store.Channel().RemoveMember(channel.Id, c.Session.UserId); cmresult.Err != nil { - c.Err = cmresult.Err - return - } - - RemoveUserFromChannel(c.Session.UserId, c.Session.UserId, channel) - - go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username), model.POST_JOIN_LEAVE) - - result := make(map[string]string) - result["id"] = channel.Id - w.Write([]byte(model.MapToJson(result))) } + + result := make(map[string]string) + result["id"] = id + w.Write([]byte(model.MapToJson(result))) } func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { @@ -483,181 +430,99 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["channel_id"] - sc := app.Srv.Store.Channel().Get(id, true) - scm := app.Srv.Store.Channel().GetMember(id, c.Session.UserId) - cmc := app.Srv.Store.Channel().GetMemberCount(id, false) - uc := app.Srv.Store.User().Get(c.Session.UserId) - ihc := app.Srv.Store.Webhook().GetIncomingByChannel(id) - ohc := app.Srv.Store.Webhook().GetOutgoingByChannel(id) - - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err - return - } else if uresult := <-uc; uresult.Err != nil { - c.Err = cresult.Err - return - } else if scmresult := <-scm; scmresult.Err != nil { - c.Err = scmresult.Err - return - } else if cmcresult := <-cmc; cmcresult.Err != nil { - c.Err = cmcresult.Err - return - } else if ihcresult := <-ihc; ihcresult.Err != nil { - c.Err = ihcresult.Err - return - } else if ohcresult := <-ohc; ohcresult.Err != nil { - c.Err = ohcresult.Err + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(id); err != nil { + c.Err = err return - } else { - channel := cresult.Data.(*model.Channel) - memberCount := cmcresult.Data.(int64) - 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 - - // Allow delete if user is the only member left in channel - if memberCount > 1 { - if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_DELETE_PUBLIC_CHANNEL) { - return - } + } - if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_DELETE_PRIVATE_CHANNEL) { - return - } - } + var memberCount int64 + if memberCount, err = app.GetChannelMemberCount(id); err != nil { + c.Err = err + return + } - if channel.DeleteAt > 0 { - c.Err = model.NewLocAppError("deleteChannel", "api.channel.delete_channel.deleted.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + // Allow delete if user is the only member left in channel + if memberCount > 1 { + if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_DELETE_PUBLIC_CHANNEL) { return } - if channel.Name == model.DEFAULT_CHANNEL { - c.Err = model.NewLocAppError("deleteChannel", "api.channel.delete_channel.cannot.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") - c.Err.StatusCode = http.StatusBadRequest + if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_DELETE_PRIVATE_CHANNEL) { return } + } - post := &model.Post{ - ChannelId: channel.Id, - Message: fmt.Sprintf(c.T("api.channel.delete_channel.archived"), user.Username), - Type: model.POST_CHANNEL_DELETED, - UserId: c.Session.UserId, - } - - if _, err := app.CreatePost(post, c.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 := <-app.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 := <-app.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 := <-app.Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil { - c.Err = dresult.Err - return - } - app.InvalidateCacheForChannel(channel.Id) - - c.LogAudit("name=" + channel.Name) - - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_DELETED, c.TeamId, "", "", nil) - message.Add("channel_id", channel.Id) + err = app.DeleteChannel(channel, c.Session.UserId) + if err != nil { + c.Err = err + return + } - app.Publish(message) + c.LogAudit("name=" + channel.Name) - result := make(map[string]string) - result["id"] = channel.Id - w.Write([]byte(model.MapToJson(result))) - } + result := make(map[string]string) + result["id"] = channel.Id + w.Write([]byte(model.MapToJson(result))) } func getChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["channel_id"] - cchan := app.Srv.Store.Channel().Get(id, true) - cmchan := app.Srv.Store.Channel().GetMember(id, c.Session.UserId) - - if cresult := <-cchan; cresult.Err != nil { - c.Err = cresult.Err - return - } else if cmresult := <-cmchan; cmresult.Err != nil { - c.Err = cmresult.Err + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(id); err != nil { + c.Err = err return - } else { - data := &model.ChannelData{} - data.Channel = cresult.Data.(*model.Channel) - member := cmresult.Data.(model.ChannelMember) - data.Member = &member - - if data.Channel.TeamId != c.TeamId && data.Channel.Type != model.CHANNEL_DIRECT { - c.Err = model.NewLocAppError("getChannel", "api.channel.get_channel.wrong_team.app_error", map[string]interface{}{"ChannelId": id, "TeamId": c.TeamId}, "") - return - } + } - if HandleEtag(data.Etag(), "Get Channel", w, r) { - return - } else { - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) - } + if channel.TeamId != c.TeamId && channel.Type != model.CHANNEL_DIRECT { + c.Err = model.NewLocAppError("getChannel", "api.channel.get_channel.wrong_team.app_error", map[string]interface{}{"ChannelId": id, "TeamId": c.TeamId}, "") + return } -} -func SetActiveChannel(userId string, channelId string) *model.AppError { - status, err := app.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() + var member *model.ChannelMember + if member, err = app.GetChannelMember(id, c.Session.UserId); err != nil { + c.Err = err + return } - app.AddStatusCache(status) + data := &model.ChannelData{} + data.Channel = channel + data.Member = member - return nil + if HandleEtag(data.Etag(), "Get Channel", w, r) { + return + } else { + w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) + w.Write([]byte(data.ToJson())) + } } func getChannelByName(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelName := params["channel_name"] - cchan := app.Srv.Store.Channel().GetByName(c.TeamId, channelName) - - if cresult := <-cchan; cresult.Err != nil { - c.Err = cresult.Err + if channel, err := app.GetChannelByName(channelName, c.TeamId); err != nil { + c.Err = err return } else { - data := cresult.Data.(*model.Channel) - - if !HasPermissionToChannelContext(c, data.Id, model.PERMISSION_READ_CHANNEL) { + if !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_READ_CHANNEL) { return } - if data.TeamId != c.TeamId && data.Type != model.CHANNEL_DIRECT { + if channel.TeamId != c.TeamId && channel.Type != model.CHANNEL_DIRECT { c.Err = model.NewLocAppError("getChannel", "api.channel.get_channel.wrong_team.app_error", map[string]interface{}{"ChannelName": channelName, "TeamId": c.TeamId}, "") return } - if HandleEtag(data.Etag(), "Get Channel By Name", w, r) { + if HandleEtag(channel.Etag(), "Get Channel By Name", w, r) { return } else { - w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) - w.Write([]byte(data.ToJson())) + w.Header().Set(model.HEADER_ETAG_SERVER, channel.Etag()) + w.Write([]byte(channel.ToJson())) } } } @@ -666,33 +531,29 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["channel_id"] - sc := app.Srv.Store.Channel().Get(id, true) var channel *model.Channel - if result := <-sc; result.Err != nil { - c.Err = result.Err + var err *model.AppError + if channel, err = app.GetChannel(id); err != nil { + c.Err = err return - } else { - channel = result.Data.(*model.Channel) } - if result := <-app.Srv.Store.Channel().GetMemberCount(id, true); result.Err != nil { - c.Err = result.Err + if channel.DeleteAt > 0 { + c.Err = model.NewLocAppError("getChannelStats", "api.channel.get_channel_extra_info.deleted.app_error", nil, "") + c.Err.StatusCode = http.StatusBadRequest return - } else { - memberCount := result.Data.(int64) - - if channel.DeleteAt > 0 { - c.Err = model.NewLocAppError("getChannelStats", "api.channel.get_channel_extra_info.deleted.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } + } - if !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_READ_CHANNEL) { - return - } + if !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_READ_CHANNEL) { + return + } - data := model.ChannelStats{ChannelId: channel.Id, MemberCount: memberCount} - w.Write([]byte(data.ToJson())) + if memberCount, err := app.GetChannelMemberCount(id); err != nil { + c.Err = err + return + } else { + stats := model.ChannelStats{ChannelId: channel.Id, MemberCount: memberCount} + w.Write([]byte(stats.ToJson())) } } @@ -705,22 +566,20 @@ func getChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Channel().GetMember(channelId, userId); result.Err != nil { - c.Err = result.Err + if member, err := app.GetChannelMember(channelId, userId); err != nil { + c.Err = err return } else { - member := result.Data.(model.ChannelMember) w.Write([]byte(member.ToJson())) } } func getMyChannelMembers(c *Context, w http.ResponseWriter, r *http.Request) { - if result := <-app.Srv.Store.Channel().GetMembersForUser(c.TeamId, c.Session.UserId); result.Err != nil { - c.Err = result.Err + if members, err := app.GetChannelMembersForUser(c.TeamId, c.Session.UserId); err != nil { + c.Err = err return } else { - data := result.Data.(*model.ChannelMembers) - w.Write([]byte(data.ToJson())) + w.Write([]byte(members.ToJson())) } } @@ -736,47 +595,45 @@ func addMember(c *Context, w http.ResponseWriter, r *http.Request) { return } - sc := app.Srv.Store.Channel().Get(id, true) - ouc := app.Srv.Store.User().Get(c.Session.UserId) - nuc := app.Srv.Store.User().Get(userId) - if nresult := <-nuc; nresult.Err != nil { - c.Err = model.NewLocAppError("addMember", "api.channel.add_member.find_user.app_error", nil, "") + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(id); err != nil { + c.Err = err return - } else if cresult := <-sc; cresult.Err != nil { - c.Err = model.NewLocAppError("addMember", "api.channel.add_member.find_channel.app_error", nil, "") + } + + if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { return - } else { - channel := cresult.Data.(*model.Channel) - nUser := nresult.Data.(*model.User) + } - if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { - return - } + if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { + return + } - if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { - return - } + var nUser *model.User + if nUser, err = app.GetUser(userId); err != nil { + c.Err = model.NewLocAppError("addMember", "api.channel.add_member.find_user.app_error", nil, err.Error()) + return + } - if oresult := <-ouc; oresult.Err != nil { - c.Err = model.NewLocAppError("addMember", "api.channel.add_member.user_adding.app_error", nil, "") - return - } else { - oUser := oresult.Data.(*model.User) + cm, err := app.AddUserToChannel(nUser, channel) + if err != nil { + c.Err = err + return + } - cm, err := app.AddUserToChannel(nUser, channel) - if err != nil { - c.Err = err - return - } + c.LogAudit("name=" + channel.Name + " user_id=" + userId) - c.LogAudit("name=" + channel.Name + " user_id=" + userId) + var oUser *model.User + if oUser, err = app.GetUser(c.Session.UserId); err != nil { + c.Err = model.NewLocAppError("addMember", "api.channel.add_member.user_adding.app_error", nil, err.Error()) + return + } - go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username), model.POST_ADD_REMOVE) + go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username), model.POST_ADD_REMOVE) - <-app.Srv.Store.Channel().UpdateLastViewedAt([]string{id}, oUser.Id) - w.Write([]byte(cm.ToJson())) - } - } + app.UpdateChannelLastViewedAt([]string{id}, oUser.Id) + w.Write([]byte(cm.ToJson())) } func removeMember(c *Context, w http.ResponseWriter, r *http.Request) { @@ -791,78 +648,45 @@ func removeMember(c *Context, w http.ResponseWriter, r *http.Request) { return } - sc := app.Srv.Store.Channel().Get(channelId, true) - cmc := app.Srv.Store.Channel().GetMember(channelId, c.Session.UserId) - ouc := app.Srv.Store.User().Get(userIdToRemove) - - if oresult := <-ouc; oresult.Err != nil { - c.Err = model.NewLocAppError("removeMember", "api.channel.remove_member.user.app_error", nil, "") + var channel *model.Channel + var err *model.AppError + if channel, err = app.GetChannel(channelId); err != nil { + c.Err = err return - } else { - oUser := oresult.Data.(*model.User) - - if cresult := <-sc; cresult.Err != nil { - c.Err = cresult.Err - return - } else if cmcresult := <-cmc; cmcresult.Err != nil { - c.Err = cmcresult.Err - return - } else { - channel := cresult.Data.(*model.Channel) - - if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { - return - } - - if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { - return - } - - if err := RemoveUserFromChannel(userIdToRemove, c.Session.UserId, channel); err != nil { - c.Err = model.NewLocAppError("updateChannel", "api.channel.remove_member.unable.app_error", nil, err.Message) - return - } - - c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove) - - go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.remove_member.removed"), oUser.Username), model.POST_ADD_REMOVE) + } - result := make(map[string]string) - result["channel_id"] = channel.Id - result["removed_user_id"] = userIdToRemove - w.Write([]byte(model.MapToJson(result))) - } + if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) { + return } -} -func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel *model.Channel) *model.AppError { - if channel.DeleteAt > 0 { - return model.NewLocAppError("RemoveUserFromChannel", "api.channel.remove_user_from_channel.deleted.app_error", nil, "") + if channel.Type == model.CHANNEL_PRIVATE && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PRIVATE_CHANNEL_MEMBERS) { + return } - if channel.Name == model.DEFAULT_CHANNEL { - return model.NewLocAppError("RemoveUserFromChannel", "api.channel.remove.default.app_error", map[string]interface{}{"Channel": model.DEFAULT_CHANNEL}, "") + if _, err = app.GetChannelMember(channel.Id, c.Session.UserId); err != nil { + c.Err = err + return } - if cmresult := <-app.Srv.Store.Channel().RemoveMember(channel.Id, userIdToRemove); cmresult.Err != nil { - return cmresult.Err + if err = app.RemoveUserFromChannel(userIdToRemove, c.Session.UserId, channel); err != nil { + c.Err = model.NewLocAppError("removeMember", "api.channel.remove_member.unable.app_error", nil, err.Message) + return } - app.InvalidateCacheForUser(userIdToRemove) - app.InvalidateCacheForChannel(channel.Id) + c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove) - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil) - message.Add("user_id", userIdToRemove) - message.Add("remover_id", removerUserId) - go app.Publish(message) + var user *model.User + if user, err = app.GetUser(userIdToRemove); err != nil { + c.Err = err + return + } - // 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 app.Publish(userMsg) + go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.remove_member.removed"), user.Username), model.POST_ADD_REMOVE) - return nil + result := make(map[string]string) + result["channel_id"] = channel.Id + result["removed_user_id"] = userIdToRemove + w.Write([]byte(model.MapToJson(result))) } func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { @@ -870,13 +694,13 @@ func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { userId := data["user_id"] if len(userId) != 26 { - c.SetInvalidParam("updateMarkUnreadLevel", "user_id") + c.SetInvalidParam("updateNotifyProps", "user_id") return } channelId := data["channel_id"] if len(channelId) != 26 { - c.SetInvalidParam("updateMarkUnreadLevel", "channel_id") + c.SetInvalidParam("updateNotifyProps", "channel_id") return } @@ -884,33 +708,13 @@ func updateNotifyProps(c *Context, w http.ResponseWriter, r *http.Request) { return } - result := <-app.Srv.Store.Channel().GetMember(channelId, userId) - if result.Err != nil { - c.Err = result.Err - return - } - - member := result.Data.(model.ChannelMember) - - // 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 := <-app.Srv.Store.Channel().UpdateMember(&member); result.Err != nil { - c.Err = result.Err + member, err := app.UpdateChannelMemberNotifyProps(data, channelId, userId) + if err != nil { + c.Err = err return - } else { - app.InvalidateCacheForUser(userId) - - // return the updated notify properties including any unchanged ones - w.Write([]byte(model.MapToJson(member.NotifyProps))) } + w.Write([]byte(model.MapToJson(member.NotifyProps))) } func searchMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) { @@ -931,11 +735,10 @@ func searchMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Channel().SearchMore(c.Session.UserId, c.TeamId, props.Term); result.Err != nil { - c.Err = result.Err + if channels, err := app.SearchChannelsUserNotIn(c.TeamId, c.Session.UserId, props.Term); err != nil { + c.Err = err return } else { - channels := result.Data.(*model.ChannelList) w.Write([]byte(channels.ToJson())) } } @@ -949,22 +752,19 @@ func autocompleteChannels(c *Context, w http.ResponseWriter, r *http.Request) { } } - var channels *model.ChannelList - - if result := <-app.Srv.Store.Channel().SearchInTeam(c.TeamId, term); result.Err != nil { - c.Err = result.Err + if channels, err := app.SearchChannels(c.TeamId, term); err != nil { + c.Err = err return } else { - channels = result.Data.(*model.ChannelList) + w.Write([]byte(channels.ToJson())) } - w.Write([]byte(channels.ToJson())) } func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) { view := model.ChannelViewFromJson(r.Body) - if err := SetActiveChannel(c.Session.UserId, view.ChannelId); err != nil { + if err := app.SetActiveChannel(c.Session.UserId, view.ChannelId); err != nil { c.Err = err return } @@ -974,39 +774,11 @@ func viewChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - channelIds := []string{view.ChannelId} - - var pchan store.StoreChannel - if len(view.PrevChannelId) > 0 { - channelIds = append(channelIds, view.PrevChannelId) - - if *utils.Cfg.EmailSettings.SendPushNotifications && !c.Session.IsMobileApp() { - pchan = app.Srv.Store.User().GetUnreadCountForChannel(c.Session.UserId, view.ChannelId) - } - } - - uchan := app.Srv.Store.Channel().UpdateLastViewedAt(channelIds, c.Session.UserId) - - if pchan != nil { - if result := <-pchan; result.Err != nil { - c.Err = result.Err - return - } else { - if result.Data.(int64) > 0 { - app.ClearPushNotification(c.Session.UserId, view.ChannelId) - } - } - } - - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if err := app.ViewChannel(view, c.TeamId, c.Session.UserId, !c.Session.IsMobileApp()); err != nil { + c.Err = err return } - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, c.TeamId, "", c.Session.UserId, nil) - message.Add("channel_id", view.ChannelId) - go app.Publish(message) - ReturnStatusOK(w) } @@ -1024,13 +796,11 @@ func getChannelMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) return } - if result := <-app.Srv.Store.Channel().GetMembersByIds(channelId, userIds); result.Err != nil { - c.Err = result.Err + if members, err := app.GetChannelMembersByIds(channelId, userIds); err != nil { + c.Err = err return } else { - members := result.Data.(model.ChannelMembers) w.Write([]byte(members.ToJson())) - return } } @@ -1046,35 +816,21 @@ func updateChannelMemberRoles(c *Context, w http.ResponseWriter, r *http.Request return } - mchan := app.Srv.Store.Channel().GetMember(channelId, userId) - - newRoles := props["new_roles"] - if !(model.IsValidUserRoles(newRoles)) { - c.SetInvalidParam("updateChannelMemberRoles", "new_roles") - return - } - if !HasPermissionToChannelContext(c, channelId, model.PERMISSION_MANAGE_CHANNEL_ROLES) { return } - var member model.ChannelMember - if result := <-mchan; result.Err != nil { - c.Err = result.Err + newRoles := props["new_roles"] + if !(model.IsValidUserRoles(newRoles)) { + c.SetInvalidParam("updateChannelMemberRoles", "new_roles") return - } else { - member = result.Data.(model.ChannelMember) } - member.Roles = newRoles - - if result := <-app.Srv.Store.Channel().UpdateMember(&member); result.Err != nil { - c.Err = result.Err + if _, err := app.UpdateChannelMemberRoles(channelId, userId, newRoles); err != nil { + c.Err = err return } - app.InvalidateCacheForUser(userId) - rdata := map[string]string{} rdata["status"] = "ok" w.Write([]byte(model.MapToJson(rdata))) diff --git a/api/command_invite_people.go b/api/command_invite_people.go index f3818628f..ef2449ebc 100644 --- a/api/command_invite_people.go +++ b/api/command_invite_people.go @@ -41,9 +41,6 @@ func (me *InvitePeopleProvider) DoCommand(c *Context, args *model.CommandArgs, m return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.email_off")} } - tchan := app.Srv.Store.Team().Get(c.TeamId) - uchan := app.Srv.Store.User().Get(c.Session.UserId) - emailList := strings.Fields(message) for i := len(emailList) - 1; i >= 0; i-- { @@ -57,23 +54,10 @@ func (me *InvitePeopleProvider) DoCommand(c *Context, args *model.CommandArgs, m return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.no_email")} } - var team *model.Team - if result := <-tchan; result.Err != nil { - c.Err = result.Err + if err := app.InviteNewUsersToTeam(emailList, c.TeamId, c.Session.UserId, c.GetSiteURL()); err != nil { + c.Err = err return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.fail")} - } else { - team = result.Data.(*model.Team) } - var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.fail")} - } else { - user = result.Data.(*model.User) - } - - go InviteMembers(team, user.GetDisplayName(), emailList, c.GetSiteURL()) - return &model.CommandResponse{ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL, Text: c.T("api.command.invite_people.sent")} } diff --git a/api/context.go b/api/context.go index 349cbd5b6..e77f6da24 100644 --- a/api/context.go +++ b/api/context.go @@ -26,9 +26,9 @@ type Context struct { IpAddress string Path string Err *model.AppError + siteURL string teamURLValid bool teamURL string - siteURL string T goi18n.TranslateFunc Locale string TeamId string diff --git a/api/deprecated.go b/api/deprecated.go index bbaafdf15..765c3aac1 100644 --- a/api/deprecated.go +++ b/api/deprecated.go @@ -72,7 +72,7 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) { } go func() { - if err := SetActiveChannel(c.Session.UserId, id); err != nil { + if err := app.SetActiveChannel(c.Session.UserId, id); err != nil { l4g.Error(err.Error()) } }() @@ -155,7 +155,7 @@ func setActiveChannel(c *Context, w http.ResponseWriter, r *http.Request) { return } - if err := SetActiveChannel(c.Session.UserId, channelId); err != nil { + if err := app.SetActiveChannel(c.Session.UserId, channelId); err != nil { c.Err = err return } diff --git a/api/file.go b/api/file.go index b0cedde40..fdc9a8747 100644 --- a/api/file.go +++ b/api/file.go @@ -46,8 +46,6 @@ const ( RotatedCCW = 6 RotatedCCWMirrored = 7 RotatedCW = 8 - - MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image ) func InitFile() { @@ -162,7 +160,7 @@ func doUploadFile(teamId string, channelId string, userId string, rawFilename st if info.IsImage() { // Check dimensions before loading the whole thing into memory later on - if info.Width*info.Height > MaxImageSize { + if info.Width*info.Height > model.MaxImageSize { err := model.NewLocAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "") err.StatusCode = http.StatusBadRequest return nil, err diff --git a/api/oauth.go b/api/oauth.go index b378eb2a3..538831ee0 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -830,17 +830,22 @@ func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, user = result.Data.(*model.User) } - RevokeAllSession(c, user.Id) - if c.Err != nil { + if err := app.RevokeAllSessions(user.Id); err != nil { + c.Err = err return } + c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil { c.Err = result.Err return } - go sendSignInChangeEmail(c, user.Email, c.GetSiteURL(), strings.Title(service)+" SSO") + go func() { + if err := app.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() } func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api/slackimport.go b/api/slackimport.go index 9d432b987..a7d50b1b9 100644 --- a/api/slackimport.go +++ b/api/slackimport.go @@ -413,7 +413,7 @@ func SlackUploadFile(sPost SlackPost, uploads map[string]*zip.File, teamId strin } func deactivateSlackBotUser(user *model.User) { - _, err := UpdateActive(user, false) + _, err := app.UpdateActive(user, false) if err != nil { l4g.Warn(utils.T("api.slackimport.slack_deactivate_bot_user.failed_to_deactivate", err)) } diff --git a/api/team.go b/api/team.go index 6f907a513..574c22de5 100644 --- a/api/team.go +++ b/api/team.go @@ -5,11 +5,8 @@ package api import ( "bytes" - "fmt" - "html/template" "io" "net/http" - "net/url" "strconv" "strings" @@ -18,7 +15,6 @@ import ( "github.com/mattermost/platform/app" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -61,14 +57,12 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { } var user *model.User + var err *model.AppError if len(c.Session.UserId) > 0 { - uchan := app.Srv.Store.User().Get(c.Session.UserId) - - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if user, err = app.GetUser(c.Session.UserId); err != nil { + c.Err = err return } else { - user = result.Data.(*model.User) team.Email = user.Email } } @@ -94,66 +88,6 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(rteam.ToJson())) } -func LeaveTeam(team *model.Team, user *model.User) *model.AppError { - - var teamMember model.TeamMember - - if result := <-app.Srv.Store.Team().GetMember(team.Id, user.Id); result.Err != nil { - return model.NewLocAppError("RemoveUserFromTeam", "api.team.remove_user_from_team.missing.app_error", nil, result.Err.Error()) - } else { - teamMember = result.Data.(model.TeamMember) - } - - var channelList *model.ChannelList - - if result := <-app.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 { - app.InvalidateCacheForChannel(channel.Id) - if result := <-app.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) - app.Publish(message) - - teamMember.Roles = "" - teamMember.DeleteAt = model.GetMillis() - - if result := <-app.Srv.Store.Team().UpdateMember(&teamMember); result.Err != nil { - return result.Err - } - - if uua := <-app.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 := <-app.Srv.Store.Preference().DeleteCategory(user.Id, team.Id); result.Err != nil { - return result.Err - } - - app.RemoveAllSessionsForUserId(user.Id) - app.InvalidateCacheForUser(user.Id) - - return nil -} - func isTeamCreationAllowed(c *Context, email string) bool { email = strings.ToLower(email) @@ -164,8 +98,7 @@ func isTeamCreationAllowed(c *Context, email string) bool { } c.Err = nil - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err == nil { - user := result.Data.(*model.User) + if user, err := app.GetUserByEmail(email); err == nil { if len(user.AuthService) > 0 && len(*user.AuthData) > 0 { return true } @@ -192,77 +125,50 @@ func isTeamCreationAllowed(c *Context, email string) bool { } func GetAllTeamListings(c *Context, w http.ResponseWriter, r *http.Request) { - if result := <-app.Srv.Store.Team().GetAllTeamListing(); result.Err != nil { - c.Err = result.Err + var teams []*model.Team + var err *model.AppError + + if teams, err = app.GetAllOpenTeams(); err != nil { + c.Err = err return - } else { - teams := result.Data.([]*model.Team) - m := make(map[string]*model.Team) - for _, v := range teams { - m[v.Id] = v - if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { - m[v.Id].Sanitize() - } - c.Err = nil - } + } - w.Write([]byte(model.TeamMapToJson(m))) + m := make(map[string]*model.Team) + for _, v := range teams { + m[v.Id] = v + if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { + m[v.Id].Sanitize() + } + c.Err = nil } + + w.Write([]byte(model.TeamMapToJson(m))) } // Gets all teams which the current user can has access to. If the user is a System Admin, this will be all teams // on the server. Otherwise, it will only be the teams of which the user is a member. func getAll(c *Context, w http.ResponseWriter, r *http.Request) { - var tchan store.StoreChannel + var teams []*model.Team + var err *model.AppError + if HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { - tchan = app.Srv.Store.Team().GetAll() + teams, err = app.GetAllTeams() } else { c.Err = nil - tchan = app.Srv.Store.Team().GetTeamsByUserId(c.Session.UserId) + teams, err = app.GetTeamsForUser(c.Session.UserId) } - if result := <-tchan; result.Err != nil { - c.Err = result.Err + if err != nil { + c.Err = err return - } else { - teams := result.Data.([]*model.Team) - m := make(map[string]*model.Team) - for _, v := range teams { - m[v.Id] = v - } - - w.Write([]byte(model.TeamMapToJson(m))) } -} - -func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) { - props := model.MapFromJson(r.Body) - id := props["id"] - if result := <-app.Srv.Store.Session().Get(id); result.Err != nil { - c.Err = result.Err - return - } else { - session := result.Data.(*model.Session) - - c.LogAudit("revoked_all=" + id) - - if session.IsOAuth { - if err := app.RevokeAccessToken(session.Token); err != nil { - c.Err = err - return - } - } else { - if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil { - c.Err = result.Err - return - } else { - app.RemoveAllSessionsForUserId(session.UserId) - w.Write([]byte(model.MapToJson(props))) - return - } - } + m := make(map[string]*model.Team) + for _, v := range teams { + m[v.Id] = v } + + w.Write([]byte(model.TeamMapToJson(m))) } func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { @@ -286,31 +192,15 @@ func inviteMembers(c *Context, w http.ResponseWriter, r *http.Request) { } } - tchan := app.Srv.Store.Team().Get(c.TeamId) - uchan := app.Srv.Store.User().Get(c.Session.UserId) - - var team *model.Team - if result := <-tchan; result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - - var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err - return - } else { - user = result.Data.(*model.User) - } - emailList := make([]string, len(invites.Invites)) for _, invite := range invites.Invites { emailList = append(emailList, invite["email"]) } - InviteMembers(team, user.GetDisplayName(), emailList, c.GetSiteURL()) + if err := app.InviteNewUsersToTeam(emailList, c.TeamId, c.Session.UserId, c.GetSiteURL()); err != nil { + c.Err = err + return + } w.Write([]byte(invites.ToJson())) } @@ -324,31 +214,24 @@ func addUserToTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } - tchan := app.Srv.Store.Team().Get(c.TeamId) - uchan := app.Srv.Store.User().Get(userId) - var team *model.Team - if result := <-tchan; result.Err != nil { - c.Err = result.Err + var err *model.AppError + if team, err = app.GetTeam(c.TeamId); err != nil { + c.Err = err return - } else { - team = result.Data.(*model.Team) } - var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if !HasPermissionToTeamContext(c, team.Id, model.PERMISSION_ADD_USER_TO_TEAM) { return - } else { - user = result.Data.(*model.User) } - if !HasPermissionToTeamContext(c, team.Id, model.PERMISSION_ADD_USER_TO_TEAM) { + var user *model.User + if user, err = app.GetUser(userId); err != nil { + c.Err = err return } - err := app.JoinUserToTeam(team, user) - if err != nil { + if err := app.JoinUserToTeam(team, user); err != nil { c.Err = err return } @@ -365,23 +248,17 @@ func removeUserFromTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } - tchan := app.Srv.Store.Team().Get(c.TeamId) - uchan := app.Srv.Store.User().Get(userId) - var team *model.Team - if result := <-tchan; result.Err != nil { - c.Err = result.Err + var err *model.AppError + if team, err = app.GetTeam(c.TeamId); err != nil { + c.Err = err return - } else { - team = result.Data.(*model.Team) } var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if user, err = app.GetUser(userId); err != nil { + c.Err = err return - } else { - user = result.Data.(*model.User) } if c.Session.UserId != user.Id { @@ -390,8 +267,7 @@ func removeUserFromTeam(c *Context, w http.ResponseWriter, r *http.Request) { } } - err := LeaveTeam(team, user) - if err != nil { + if err := app.LeaveTeam(team, user); err != nil { c.Err = err return } @@ -400,73 +276,26 @@ func removeUserFromTeam(c *Context, w http.ResponseWriter, r *http.Request) { } func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) { - params := model.MapFromJson(r.Body) hash := params["hash"] data := params["data"] inviteId := params["invite_id"] - teamId := "" var team *model.Team + var err *model.AppError if len(hash) > 0 { - props := model.MapFromJson(strings.NewReader(data)) - - if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { - c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_invalid.app_error", nil, "") - return - } - - t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours - c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_expired.app_error", nil, "") - return - } - - teamId = props["id"] - - // try to load the team to make sure it exists - if result := <-app.Srv.Store.Team().Get(teamId); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - } - } - - if len(inviteId) > 0 { - if result := <-app.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { - c.Err = result.Err - return - } else { - team = result.Data.(*model.Team) - teamId = team.Id - } - } - - if len(teamId) == 0 { + team, err = app.JoinUserToTeamByHash(c.Session.UserId, hash, data) + } else if len(inviteId) > 0 { + team, err = app.JoinUserToTeamByInviteId(inviteId, c.Session.UserId) + } else { c.Err = model.NewLocAppError("addUserToTeamFromInvite", "api.user.create_user.signup_link_invalid.app_error", nil, "") return } - uchan := app.Srv.Store.User().Get(c.Session.UserId) - - var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if err != nil { + c.Err = err return - } else { - user = result.Data.(*model.User) - } - - tm := c.Session.GetTeamByTeamId(teamId) - - if tm == nil { - err := app.JoinUserToTeam(team, user) - if err != nil { - c.Err = err - return - } } team.Sanitize() @@ -474,20 +303,12 @@ func addUserToTeamFromInvite(c *Context, w http.ResponseWriter, r *http.Request) w.Write([]byte(team.ToJson())) } -func FindTeamByName(name string) bool { - if result := <-app.Srv.Store.Team().GetByName(name); result.Err != nil { - return false - } else { - return true - } -} - func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { m := model.MapFromJson(r.Body) name := strings.ToLower(strings.TrimSpace(m["name"])) - found := FindTeamByName(name) + found := app.FindTeamByName(name) if found { w.Write([]byte("true")) @@ -500,12 +321,10 @@ func getTeamByName(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) teamname := params["team_name"] - if result := <-app.Srv.Store.Team().GetByName(teamname); result.Err != nil { - c.Err = result.Err + if team, err := app.GetTeamByName(teamname); err != nil { + c.Err = err return } else { - team := result.Data.(*model.Team) - if team.Type != model.TEAM_OPEN && c.Session.GetTeamByTeamId(team.Id) == nil { if !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { return @@ -521,12 +340,11 @@ func getMyTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { if len(c.Session.TeamMembers) > 0 { w.Write([]byte(model.TeamMembersToJson(c.Session.TeamMembers))) } else { - if result := <-app.Srv.Store.Team().GetTeamsForUser(c.Session.UserId); result.Err != nil { - c.Err = result.Err + if members, err := app.GetTeamMembersForUser(c.Session.UserId); err != nil { + c.Err = err return } else { - data := result.Data.([]*model.TeamMember) - w.Write([]byte(model.TeamMembersToJson(data))) + w.Write([]byte(model.TeamMembersToJson(members))) } } } @@ -534,79 +352,11 @@ func getMyTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { func getMyTeamsUnread(c *Context, w http.ResponseWriter, r *http.Request) { teamId := r.URL.Query().Get("id") - if result := <-app.Srv.Store.Team().GetTeamsUnreadForUser(teamId, c.Session.UserId); result.Err != nil { - c.Err = result.Err + if unreads, err := app.GetTeamsUnreadForUser(teamId, c.Session.UserId); err != nil { + c.Err = err return } 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) - } - w.Write([]byte(model.TeamsUnreadToJson(members))) - } -} - -func InviteMembers(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) - } - } + w.Write([]byte(model.TeamsUnreadToJson(unreads))) } } @@ -627,34 +377,22 @@ func updateTeam(c *Context, w http.ResponseWriter, r *http.Request) { return } - var oldTeam *model.Team - if result := <-app.Srv.Store.Team().Get(team.Id); result.Err != nil { - c.Err = result.Err - return - } else { - oldTeam = result.Data.(*model.Team) - } - - oldTeam.DisplayName = team.DisplayName - oldTeam.Description = team.Description - oldTeam.InviteId = team.InviteId - oldTeam.AllowOpenInvite = team.AllowOpenInvite - oldTeam.CompanyName = team.CompanyName - oldTeam.AllowedDomains = team.AllowedDomains - //oldTeam.Type = team.Type + var err *model.AppError + var updatedTeam *model.Team - if result := <-app.Srv.Store.Team().Update(oldTeam); result.Err != nil { - c.Err = result.Err + updatedTeam, err = app.UpdateTeam(team) + if err != nil { + c.Err = err return } - oldTeam.Sanitize() + updatedTeam.Sanitize() message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_UPDATE_TEAM, "", "", "", nil) - message.Add("team", oldTeam.ToJson()) + message.Add("team", updatedTeam.ToJson()) go app.Publish(message) - w.Write([]byte(oldTeam.ToJson())) + w.Write([]byte(updatedTeam.ToJson())) } func updateMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { @@ -666,8 +404,6 @@ func updateMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { return } - mchan := app.Srv.Store.Team().GetTeamsForUser(userId) - teamId := c.TeamId newRoles := props["new_roles"] @@ -680,74 +416,30 @@ func updateMemberRoles(c *Context, w http.ResponseWriter, r *http.Request) { return } - var member *model.TeamMember - if result := <-mchan; result.Err != nil { - c.Err = result.Err - return - } else { - members := result.Data.([]*model.TeamMember) - for _, m := range members { - if m.TeamId == teamId { - member = m - } - } - } - - if member == nil { - c.Err = model.NewLocAppError("updateMemberRoles", "api.team.update_member_roles.not_a_member", nil, "userId="+userId+" teamId="+teamId) - c.Err.StatusCode = http.StatusBadRequest - return - } - - member.Roles = newRoles - - if result := <-app.Srv.Store.Team().UpdateMember(member); result.Err != nil { - c.Err = result.Err + if _, err := app.UpdateTeamMemberRoles(teamId, userId, newRoles); err != nil { + c.Err = err return } - app.RemoveAllSessionsForUserId(userId) - rdata := map[string]string{} rdata["status"] = "ok" w.Write([]byte(model.MapToJson(rdata))) } -func PermanentDeleteTeam(team *model.Team) *model.AppError { - team.DeleteAt = model.GetMillis() - if result := <-app.Srv.Store.Team().Update(team); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Channel().PermanentDeleteByTeam(team.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Team().RemoveAllMembersByTeam(team.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Team().PermanentDelete(team.Id); result.Err != nil { - return result.Err - } - - return nil -} - func getMyTeam(c *Context, w http.ResponseWriter, r *http.Request) { if len(c.TeamId) == 0 { return } - if result := <-app.Srv.Store.Team().Get(c.TeamId); result.Err != nil { - c.Err = result.Err + if team, err := app.GetTeam(c.TeamId); err != nil { + c.Err = err return - } else if HandleEtag(result.Data.(*model.Team).Etag(), "Get My Team", w, r) { + } else if HandleEtag(team.Etag(), "Get My Team", w, r) { return } else { - w.Header().Set(model.HEADER_ETAG_SERVER, result.Data.(*model.Team).Etag()) - w.Write([]byte(result.Data.(*model.Team).ToJson())) + w.Header().Set(model.HEADER_ETAG_SERVER, team.Etag()) + w.Write([]byte(team.ToJson())) return } } @@ -759,24 +451,10 @@ func getTeamStats(c *Context, w http.ResponseWriter, r *http.Request) { } } - tchan := app.Srv.Store.Team().GetTotalMemberCount(c.TeamId) - achan := app.Srv.Store.Team().GetActiveMemberCount(c.TeamId) - - stats := &model.TeamStats{} - stats.TeamId = c.TeamId - - if result := <-tchan; result.Err != nil { - c.Err = result.Err - return - } else { - stats.TotalMemberCount = result.Data.(int64) - } - - if result := <-achan; result.Err != nil { - c.Err = result.Err + stats, err := app.GetTeamStats(c.TeamId) + if err != nil { + c.Err = err return - } else { - stats.ActiveMemberCount = result.Data.(int64) } w.Write([]byte(stats.ToJson())) @@ -857,11 +535,10 @@ func getInviteInfo(c *Context, w http.ResponseWriter, r *http.Request) { m := model.MapFromJson(r.Body) inviteId := m["invite_id"] - if result := <-app.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { - c.Err = result.Err + if team, err := app.GetTeamByInviteId(inviteId); err != nil { + c.Err = err return } else { - team := result.Data.(*model.Team) if !(team.Type == model.TEAM_OPEN) { c.Err = model.NewLocAppError("getInviteInfo", "api.team.get_invite_info.not_open_team", nil, "id="+inviteId) return @@ -897,11 +574,10 @@ func getTeamMembers(c *Context, w http.ResponseWriter, r *http.Request) { } } - if result := <-app.Srv.Store.Team().GetMembers(c.TeamId, offset, limit); result.Err != nil { - c.Err = result.Err + if members, err := app.GetTeamMembers(c.TeamId, offset, limit); err != nil { + c.Err = err return } else { - members := result.Data.([]*model.TeamMember) w.Write([]byte(model.TeamMembersToJson(members))) return } @@ -922,11 +598,10 @@ func getTeamMember(c *Context, w http.ResponseWriter, r *http.Request) { } } - if result := <-app.Srv.Store.Team().GetMember(c.TeamId, userId); result.Err != nil { - c.Err = result.Err + if member, err := app.GetTeamMember(c.TeamId, userId); err != nil { + c.Err = err return } else { - member := result.Data.(model.TeamMember) w.Write([]byte(member.ToJson())) return } @@ -945,11 +620,10 @@ func getTeamMembersByIds(c *Context, w http.ResponseWriter, r *http.Request) { } } - if result := <-app.Srv.Store.Team().GetMembersByIds(c.TeamId, userIds); result.Err != nil { - c.Err = result.Err + if members, err := app.GetTeamMembersByIds(c.TeamId, userIds); err != nil { + c.Err = err return } else { - members := result.Data.([]*model.TeamMember) w.Write([]byte(model.TeamMembersToJson(members))) return } diff --git a/api/team_test.go b/api/team_test.go index 641f7b98d..fdc7e334b 100644 --- a/api/team_test.go +++ b/api/team_test.go @@ -191,7 +191,7 @@ func TestGetAllTeamListings(t *testing.T) { } } - UpdateUserRoles(user, model.ROLE_SYSTEM_ADMIN.Id) + app.UpdateUserRoles(user.Id, model.ROLE_SYSTEM_ADMIN.Id) Client.Login(user.Email, "passwd1") Client.SetTeamId(team.Id) @@ -244,7 +244,7 @@ func TestTeamPermDelete(t *testing.T) { c.RequestId = model.NewId() c.IpAddress = "test" - err := PermanentDeleteTeam(team) + err := app.PermanentDeleteTeam(team) if err != nil { t.Fatal(err) } diff --git a/api/user.go b/api/user.go index 7587b83ad..37c9948ed 100644 --- a/api/user.go +++ b/api/user.go @@ -8,10 +8,6 @@ import ( b64 "encoding/base64" "fmt" "html/template" - "image" - _ "image/gif" - _ "image/jpeg" - "image/png" "io" "net/http" "net/url" @@ -20,7 +16,6 @@ import ( "time" l4g "github.com/alecthomas/log4go" - "github.com/disintegration/imaging" "github.com/gorilla/mux" "github.com/mattermost/platform/app" "github.com/mattermost/platform/einterfaces" @@ -140,7 +135,9 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { } if shouldSendWelcomeEmail { - sendWelcomeEmail(c, ruser.Id, ruser.Email, c.GetSiteURL(), ruser.EmailVerified) + if err := app.SendWelcomeEmail(ruser.Id, ruser.Email, ruser.EmailVerified, ruser.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } } w.Write([]byte(ruser.ToJson())) @@ -190,55 +187,6 @@ func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool return shouldVerifyHash } -func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, verified bool) { - rawUrl, _ := url.Parse(siteURL) - - subject := c.T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host}) - - bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.welcome_body.title", map[string]interface{}{"ServerURL": rawUrl.Host}) - bodyPage.Props["Info"] = c.T("api.templates.welcome_body.info") - bodyPage.Props["Button"] = c.T("api.templates.welcome_body.button") - bodyPage.Props["Info2"] = c.T("api.templates.welcome_body.info2") - bodyPage.Props["Info3"] = c.T("api.templates.welcome_body.info3") - bodyPage.Props["SiteURL"] = siteURL - - if *utils.Cfg.NativeAppSettings.AppDownloadLink != "" { - bodyPage.Props["AppDownloadInfo"] = c.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 { - l4g.Error(utils.T("api.user.send_welcome_email_and_forget.failed.error"), err) - } -} - -func SendVerifyEmail(c *Context, userId, userEmail, siteURL string) { - 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 := c.T("api.templates.verify_subject", - map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) - - bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.verify_body.title", map[string]interface{}{"ServerURL": url.Host}) - bodyPage.Props["Info"] = c.T("api.templates.verify_body.info") - bodyPage.Props["VerifyUrl"] = link - bodyPage.Props["Button"] = c.T("api.templates.verify_body.button") - - if err := utils.SendMail(userEmail, subject, bodyPage.Render()); err != nil { - l4g.Error(utils.T("api.user.send_verify_email_and_forget.failed.error"), err) - } -} - func login(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) @@ -362,21 +310,10 @@ func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.Use maxAge = *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24 // A special case where we logout of all other sessions with the same Id - if result := <-app.Srv.Store.Session().GetSessions(user.Id); result.Err != nil { - c.Err = result.Err + if err := app.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil { + c.Err = err c.Err.StatusCode = http.StatusInternalServerError return - } else { - sessions := result.Data.([]*model.Session) - for _, session := range sessions { - if session.DeviceId == deviceId { - l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, user.Id) - if err := app.RevokeSessionById(session.Id); err != nil { - c.LogError(err) - c.Err = nil - } - } - } } } else { session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthWebInDays) @@ -399,10 +336,6 @@ func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.Use bname = "unknown" } - if strings.Contains(r.UserAgent(), "Mattermost") { - bname = "Desktop App" - } - if bversion == "" { bversion = "0.0" } @@ -411,13 +344,11 @@ func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.Use session.AddProp(model.SESSION_PROP_OS, os) session.AddProp(model.SESSION_PROP_BROWSER, fmt.Sprintf("%v/%v", bname, bversion)) - if result := <-app.Srv.Store.Session().Save(session); result.Err != nil { - c.Err = result.Err + var err *model.AppError + if session, err = app.CreateSession(session); err != nil { + c.Err = err c.Err.StatusCode = http.StatusInternalServerError return - } else { - session = result.Data.(*model.Session) - app.AddSessionToCache(session) } w.Header().Set(model.HEADER_TOKEN, session.Token) @@ -476,7 +407,7 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { return } - app.RemoveAllSessionsForUserId(c.Session.UserId) + app.ClearSessionCacheForUser(c.Session.UserId) c.Session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthMobileInDays) maxAge := *utils.Cfg.ServiceSettings.SessionLengthMobileInDays * 60 * 60 * 24 @@ -507,58 +438,6 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(props))) } -// IF YOU UPDATE THIS PLEASE UPDATE BELOW -func RevokeAllSession(c *Context, userId string) { - if result := <-app.Srv.Store.Session().GetSessions(userId); result.Err != nil { - c.Err = result.Err - return - } else { - sessions := result.Data.([]*model.Session) - - for _, session := range sessions { - c.LogAuditWithUserId(userId, "session_id="+session.Id) - if session.IsOAuth { - app.RevokeAccessToken(session.Token) - } else { - if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil { - c.Err = result.Err - return - } - } - - app.RevokeWebrtcToken(session.Id) - } - } - - app.RemoveAllSessionsForUserId(userId) -} - -// UGH... -// If you update this please update above -func RevokeAllSessionsNoContext(userId string) *model.AppError { - if result := <-app.Srv.Store.Session().GetSessions(userId); result.Err != nil { - return result.Err - } else { - sessions := result.Data.([]*model.Session) - - for _, session := range sessions { - if session.IsOAuth { - app.RevokeAccessToken(session.Token) - } else { - if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil { - return result.Err - } - } - - app.RevokeWebrtcToken(session.Id) - } - } - - app.RemoveAllSessionsForUserId(userId) - - return nil -} - func getSessions(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) @@ -568,11 +447,10 @@ func getSessions(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.Session().GetSessions(id); result.Err != nil { - c.Err = result.Err + if sessions, err := app.GetSessions(id); err != nil { + c.Err = err return } else { - sessions := result.Data.([]*model.Session) for _, session := range sessions { session.Sanitize() } @@ -990,64 +868,11 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { imageData := imageArray[0] - file, err := imageData.Open() - defer file.Close() - if err != nil { - c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.open.app_error", nil, err.Error()) - return - } - - // Decode image config first to check dimensions before loading the whole thing into memory later on - config, _, err := image.DecodeConfig(file) - if err != nil { - c.Err = model.NewLocAppError("uploadProfileFile", "api.user.upload_profile_user.decode_config.app_error", nil, err.Error()) - return - } else if config.Width*config.Height > MaxImageSize { - c.Err = model.NewLocAppError("uploadProfileFile", "api.user.upload_profile_user.too_large.app_error", nil, err.Error()) - return - } - - file.Seek(0, 0) - - // Decode image into Image object - img, _, err := image.Decode(file) - if err != nil { - c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.decode.app_error", nil, err.Error()) - return - } - - // 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 { - c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.encode.app_error", nil, err.Error()) - return - } - - path := "users/" + c.Session.UserId + "/profile.png" - - if err := app.WriteFile(buf.Bytes(), path); err != nil { - c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "") + if err := app.SetProfileImage(c.Session.UserId, imageData); err != nil { + c.Err = err return } - app.Srv.Store.User().UpdateLastPictureUpdate(c.Session.UserId) - - if result := <-app.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { - l4g.Error(utils.T("api.user.get_me.getting.error"), c.Session.UserId) - } else { - user := result.Data.(*model.User) - user = sanitizeProfile(c, user) - omitUsers := make(map[string]bool, 1) - omitUsers[user.Id] = true - message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", omitUsers) - message.Add("user", user) - - go app.Publish(message) - } - c.LogAudit("") // write something as the response since jQuery expects a json response @@ -1071,29 +896,13 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.User().Update(user, false); result.Err != nil { - c.Err = result.Err + if ruser, err := app.UpdateUser(user, c.GetSiteURL()); err != nil { + c.Err = err return } else { c.LogAudit("") - rusers := result.Data.([2]*model.User) - - if rusers[0].Email != rusers[1].Email { - go sendEmailChangeEmail(c, rusers[1].Email, rusers[0].Email, c.GetSiteURL()) - - if utils.Cfg.EmailSettings.RequireEmailVerification { - go SendEmailChangeVerifyEmail(c, rusers[0].Id, rusers[0].Email, c.GetSiteURL()) - } - } - - if rusers[0].Username != rusers[1].Username { - go sendEmailChangeUsername(c, rusers[1].Username, rusers[0].Username, rusers[0].Email, c.GetSiteURL()) - } - - app.InvalidateCacheForUser(user.Id) - - updatedUser := rusers[0] + updatedUser := ruser updatedUser = sanitizeProfile(c, updatedUser) omitUsers := make(map[string]bool, 1) @@ -1102,10 +911,10 @@ func updateUser(c *Context, w http.ResponseWriter, r *http.Request) { message.Add("user", updatedUser) go app.Publish(message) - rusers[0].Password = "" - rusers[0].AuthData = new(string) - *rusers[0].AuthData = "" - w.Write([]byte(rusers[0].ToJson())) + ruser.Password = "" + ruser.AuthData = new(string) + *ruser.AuthData = "" + w.Write([]byte(ruser.ToJson())) } } @@ -1138,21 +947,20 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { return } - var result store.StoreResult + var user *model.User + var err *model.AppError - if result = <-app.Srv.Store.User().Get(userId); result.Err != nil { - c.Err = result.Err + if user, err = app.GetUser(userId); err != nil { + c.Err = err return } - if result.Data == nil { + if user == nil { c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.valid_account.app_error", nil, "") c.Err.StatusCode = http.StatusBadRequest return } - user := result.Data.(*model.User) - if user.AuthData != nil && *user.AuthData != "" { c.LogAudit("failed - tried to update user password who was logged in through oauth") c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.oauth.app_error", nil, "auth_service="+user.AuthService) @@ -1170,16 +978,14 @@ func updatePassword(c *Context, w http.ResponseWriter, r *http.Request) { return } - if uresult := <-app.Srv.Store.User().UpdatePassword(c.Session.UserId, model.HashPassword(newPassword)); uresult.Err != nil { - c.Err = model.NewLocAppError("updatePassword", "api.user.update_password.failed.app_error", nil, uresult.Err.Error()) + if err := app.UpdatePasswordSendEmail(user, model.HashPassword(newPassword), c.T("api.user.update_password.menu"), c.GetSiteURL()); err != nil { + c.Err = err return } else { c.LogAudit("completed") - go sendPasswordChangeEmail(c, user.Email, c.GetSiteURL(), c.T("api.user.update_password.menu")) - data := make(map[string]string) - data["user_id"] = uresult.Data.(string) + data["user_id"] = c.Session.UserId w.Write([]byte(model.MapToJson(data))) } } @@ -1204,18 +1010,10 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { return } - var user *model.User - if result := <-app.Srv.Store.User().Get(userId); result.Err != nil { - c.Err = result.Err + if _, err := app.UpdateUserRoles(userId, newRoles); err != nil { return } else { - user = result.Data.(*model.User) - } - - if _, err := UpdateUserRoles(user, newRoles); err != nil { - return - } else { - c.LogAuditWithUserId(user.Id, "roles="+newRoles) + c.LogAuditWithUserId(userId, "roles="+newRoles) } rdata := map[string]string{} @@ -1223,34 +1021,11 @@ func updateRoles(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(rdata))) } -func UpdateUserRoles(user *model.User, newRoles string) (*model.User, *model.AppError) { - - user.Roles = newRoles - uchan := app.Srv.Store.User().Update(user, true) - schan := app.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) - } - - app.RemoveAllSessionsForUserId(user.Id) - - return ruser, nil -} - func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - user_id := props["user_id"] - if len(user_id) != 26 { + userId := props["user_id"] + if len(userId) != 26 { c.SetInvalidParam("updateActive", "user_id") return } @@ -1258,142 +1033,35 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) { active := props["active"] == "true" var user *model.User - if result := <-app.Srv.Store.User().Get(user_id); result.Err != nil { - c.Err = result.Err + var err *model.AppError + if user, err = app.GetUser(userId); err != nil { + c.Err = err return - } else { - user = result.Data.(*model.User) } // true when you're trying to de-activate yourself - isSelfDeactive := !active && user_id == c.Session.UserId + isSelfDeactive := !active && userId == c.Session.UserId if !isSelfDeactive && !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { - c.Err = model.NewLocAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+user_id) + c.Err = model.NewLocAppError("updateActive", "api.user.update_active.permissions.app_error", nil, "userId="+userId) c.Err.StatusCode = http.StatusForbidden return } if user.IsLDAPUser() { - c.Err = model.NewLocAppError("updateActive", "api.user.update_active.no_deactivate_ldap.app_error", nil, "userId="+user_id) + c.Err = model.NewLocAppError("updateActive", "api.user.update_active.no_deactivate_ldap.app_error", nil, "userId="+userId) c.Err.StatusCode = http.StatusBadRequest return } - if ruser, err := UpdateActive(user, active); err != nil { + if ruser, err := app.UpdateActive(user, active); err != nil { c.Err = err } else { - if !active { - app.SetStatusOffline(ruser.Id, false) - } - c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active)) w.Write([]byte(ruser.ToJson())) } } -func UpdateActive(user *model.User, active bool) (*model.User, *model.AppError) { - if active { - user.DeleteAt = 0 - } else { - user.DeleteAt = model.GetMillis() - } - - if result := <-app.Srv.Store.User().Update(user, true); result.Err != nil { - return nil, result.Err - } else { - if user.DeleteAt > 0 { - RevokeAllSessionsNoContext(user.Id) - } - - if extra := <-app.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) - 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 := <-app.Srv.Store.Session().PermanentDeleteSessionsByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.OAuth().PermanentDeleteAuthDataByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Webhook().PermanentDeleteIncomingByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Webhook().PermanentDeleteOutgoingByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Command().PermanentDeleteByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Preference().PermanentDeleteByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Channel().PermanentDeleteMembersByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Post().PermanentDeleteByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.User().PermanentDelete(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Audit().PermanentDeleteByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.Srv.Store.Team().RemoveAllMembersByUser(user.Id); result.Err != nil { - return result.Err - } - - if result := <-app.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 := <-app.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 sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) @@ -1404,11 +1072,10 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { } var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { + var err *model.AppError + if user, err = app.GetUserByEmail(email); err != nil { w.Write([]byte(model.MapToJson(props))) return - } else { - user = result.Data.(*model.User) } if user.AuthData != nil && len(*user.AuthData) != 0 { @@ -1416,11 +1083,9 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) { return } - recovery := &model.PasswordRecovery{} - recovery.UserId = user.Id - - if result := <-app.Srv.Store.PasswordRecovery().SaveOrUpdate(recovery); result.Err != nil { - c.Err = result.Err + var recovery *model.PasswordRecovery + if recovery, err = app.CreatePasswordRecovery(user.Id); err != nil { + c.Err = err return } @@ -1464,13 +1129,11 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { userId := "" - if result := <-app.Srv.Store.PasswordRecovery().GetByCode(code); result.Err != nil { + if recovery, err := app.GetPasswordRecovery(code); err != nil { c.LogAuditWithUserId(userId, "fail - bad code") - c.Err = model.NewLocAppError("resetPassword", "api.user.reset_password.invalid_link.app_error", nil, result.Err.Error()) + c.Err = err return } else { - recovery := result.Data.(*model.PasswordRecovery) - if model.GetMillis()-recovery.CreateAt < model.PASSWORD_RECOVER_EXPIRY_TIME { userId = recovery.UserId } else { @@ -1479,11 +1142,9 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { return } - go func() { - if result := <-app.Srv.Store.PasswordRecovery().Delete(userId); result.Err != nil { - l4g.Error("%v", result.Err) - } - }() + if err := app.DeletePasswordRecoveryForUser(userId); err != nil { + l4g.Error(err.Error()) + } } if err := ResetPassword(c, userId, newPassword); err != nil { @@ -1500,10 +1161,9 @@ func resetPassword(c *Context, w http.ResponseWriter, r *http.Request) { func ResetPassword(c *Context, userId, newPassword string) *model.AppError { var user *model.User - if result := <-app.Srv.Store.User().Get(userId); result.Err != nil { - return result.Err - } else { - user = result.Data.(*model.User) + var err *model.AppError + if user, err = app.GetUser(userId); err != nil { + return err } if user.AuthData != nil && len(*user.AuthData) != 0 && !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) { @@ -1511,116 +1171,23 @@ func ResetPassword(c *Context, userId, newPassword string) *model.AppError { } - if result := <-app.Srv.Store.User().UpdatePassword(userId, model.HashPassword(newPassword)); result.Err != nil { - return result.Err + if err := app.UpdatePasswordSendEmail(user, model.HashPassword(newPassword), c.T("api.user.reset_password.method"), c.GetSiteURL()); err != nil { + return err } - go sendPasswordChangeEmail(c, user.Email, c.GetSiteURL(), c.T("api.user.reset_password.method")) - return nil } -func sendPasswordChangeEmail(c *Context, email, siteURL, method string) { - subject := c.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", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.password_change_body.title") - bodyPage.Html["Info"] = template.HTML(c.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 { - l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err) - } -} - -func sendMfaChangeEmail(c *Context, email string, siteURL string, activated bool) { - subject := c.T("api.templates.mfa_change_subject", - map[string]interface{}{"SiteName": utils.Cfg.TeamSettings.SiteName}) - - bodyPage := utils.NewHTMLTemplate("mfa_change_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - - bodyText := "" - if activated { - bodyText = "api.templates.mfa_activated_body.info" - bodyPage.Props["Title"] = c.T("api.templates.mfa_activated_body.title") - } else { - bodyText = "api.templates.mfa_deactivated_body.info" - bodyPage.Props["Title"] = c.T("api.templates.mfa_deactivated_body.title") - } - - bodyPage.Html["Info"] = template.HTML(c.T(bodyText, - map[string]interface{}{"SiteURL": siteURL})) - - if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil { - l4g.Error(utils.T("api.user.send_mfa_change_email.error"), err) - } -} - -func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) { - subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_subject", - map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) - - bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.email_change_body.title") - bodyPage.Html["Info"] = template.HTML(c.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 { - l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err) - } -} - -func SendEmailChangeVerifyEmail(c *Context, userId, newUserEmail, siteURL string) { - 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, c.T("api.templates.email_change_verify_subject", - map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) - - bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.email_change_verify_body.title") - bodyPage.Props["Info"] = c.T("api.templates.email_change_verify_body.info", - map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}) - bodyPage.Props["VerifyUrl"] = link - bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button") - - if err := utils.SendMail(newUserEmail, subject, bodyPage.Render()); err != nil { - l4g.Error(utils.T("api.user.send_email_change_verify_email_and_forget.error"), err) - } -} - -func sendEmailChangeUsername(c *Context, oldUsername, newUsername, email, siteURL string) { - subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.username_change_subject", - map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})) - - bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.username_change_body.title") - bodyPage.Html["Info"] = template.HTML(c.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 { - l4g.Error(utils.T("api.user.send_email_change_username_and_forget.error"), err) - } - -} - func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) - user_id := props["user_id"] - if len(user_id) != 26 { + userId := props["user_id"] + if len(userId) != 26 { c.SetInvalidParam("updateUserNotify", "user_id") return } - uchan := app.Srv.Store.User().Get(user_id) - - if !HasPermissionToUser(c, user_id) { + if !HasPermissionToUser(c, userId) { return } @@ -1651,28 +1218,26 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) { } var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err + var err *model.AppError + if user, err = app.GetUser(userId); err != nil { + c.Err = err return - } else { - user = result.Data.(*model.User) } user.NotifyProps = props - if result := <-app.Srv.Store.User().Update(user, false); result.Err != nil { - c.Err = result.Err + var ruser *model.User + if ruser, err = app.UpdateUser(user, c.GetSiteURL()); err != nil { + c.Err = err return - } else { - c.LogAuditWithUserId(user.Id, "") - app.InvalidateCacheForUser(user.Id) - - ruser := result.Data.([2]*model.User)[0] - options := utils.Cfg.GetSanitizeOptions() - options["passwordupdate"] = false - ruser.Sanitize(options) - w.Write([]byte(ruser.ToJson())) } + + c.LogAuditWithUserId(user.Id, "") + + options := utils.Cfg.GetSanitizeOptions() + options["passwordupdate"] = false + ruser.Sanitize(options) + w.Write([]byte(ruser.ToJson())) } func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { @@ -1701,12 +1266,11 @@ func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { + var err *model.AppError + if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") - c.Err = result.Err + c.Err = err return - } else { - user = result.Data.(*model.User) } if err := checkPasswordAndAllCriteria(user, password, mfaToken); err != nil { @@ -1754,12 +1318,11 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { + var err *model.AppError + if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") - c.Err = result.Err + c.Err = err return - } else { - user = result.Data.(*model.User) } if user.Id != c.Session.UserId { @@ -1769,15 +1332,24 @@ func oauthToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.User().UpdatePassword(c.Session.UserId, model.HashPassword(password)); result.Err != nil { + if err := app.UpdatePassword(user, model.HashPassword(password)); err != nil { c.LogAudit("fail - database issue") - c.Err = result.Err + c.Err = err return } - go sendSignInChangeEmail(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) + go func() { + if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() + + if err := app.RevokeAllSessions(c.Session.UserId); err != nil { + c.Err = err + return + } + c.LogAuditWithUserId(c.Session.UserId, "Revoked all sessions for user") - RevokeAllSession(c, c.Session.UserId) c.RemoveSessionCookie(w, r) if c.Err != nil { return @@ -1822,12 +1394,11 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { + var err *model.AppError + if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") - c.Err = result.Err + c.Err = err return - } else { - user = result.Data.(*model.User) } if err := checkPasswordAndAllCriteria(user, emailPassword, token); err != nil { @@ -1836,7 +1407,12 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { return } - RevokeAllSession(c, user.Id) + if err := app.RevokeAllSessions(user.Id); err != nil { + c.Err = err + return + } + c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") + c.RemoveSessionCookie(w, r) if c.Err != nil { return @@ -1855,7 +1431,11 @@ func emailToLdap(c *Context, w http.ResponseWriter, r *http.Request) { return } - go sendSignInChangeEmail(c, user.Email, c.GetSiteURL(), "AD/LDAP") + go func() { + if err := app.SendSignInChangeEmail(user.Email, "AD/LDAP", user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() m := map[string]string{} m["follow_link"] = "/login?extra=signin_change" @@ -1890,12 +1470,11 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { + var err *model.AppError + if user, err = app.GetUserByEmail(email); err != nil { c.LogAudit("fail - couldn't get user") - c.Err = result.Err + c.Err = err return - } else { - user = result.Data.(*model.User) } if user.AuthService != model.USER_AUTH_SERVICE_LDAP { @@ -1922,19 +1501,28 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.User().UpdatePassword(user.Id, model.HashPassword(emailPassword)); result.Err != nil { + if err := app.UpdatePassword(user, model.HashPassword(emailPassword)); err != nil { c.LogAudit("fail - database issue") - c.Err = result.Err + c.Err = err + return + } + + if err := app.RevokeAllSessions(user.Id); err != nil { + c.Err = err return } + c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") - RevokeAllSession(c, user.Id) c.RemoveSessionCookie(w, r) if c.Err != nil { return } - go sendSignInChangeEmail(c, user.Email, c.GetSiteURL(), c.T("api.templates.signin_change_email.body.method_email")) + go func() { + if err := app.SendSignInChangeEmail(user.Email, c.T("api.templates.signin_change_email.body.method_email"), user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() m := map[string]string{} m["follow_link"] = "/login?extra=signin_change" @@ -1943,21 +1531,6 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(m))) } -func sendSignInChangeEmail(c *Context, email, siteURL, method string) { - subject := c.T("api.templates.singin_change_email.subject", - map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) - - bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale) - bodyPage.Props["SiteURL"] = siteURL - bodyPage.Props["Title"] = c.T("api.templates.signin_change_email.body.title") - bodyPage.Html["Info"] = template.HTML(c.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 { - l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err) - } -} - func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) @@ -1974,7 +1547,7 @@ func verifyEmail(c *Context, w http.ResponseWriter, r *http.Request) { } if model.ComparePassword(hashedId, userId+utils.Cfg.EmailSettings.InviteSalt) { - if c.Err = (<-app.Srv.Store.User().VerifyEmail(userId)).Err; c.Err != nil { + if c.Err = app.VerifyUserEmail(userId); c.Err != nil { return } else { c.LogAudit("Email Verified") @@ -2000,22 +1573,19 @@ func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) { return } else { if _, err := app.GetStatus(user.Id); err != nil { - go SendVerifyEmail(c, user.Id, user.Email, c.GetSiteURL()) + go app.SendVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) } else { - go SendEmailChangeVerifyEmail(c, user.Id, user.Email, c.GetSiteURL()) + go app.SendEmailChangeVerifyEmail(user.Id, user.Email, user.Locale, c.GetSiteURL()) } } } func generateMfaSecret(c *Context, w http.ResponseWriter, r *http.Request) { - uchan := app.Srv.Store.User().Get(c.Session.UserId) - var user *model.User - if result := <-uchan; result.Err != nil { - c.Err = result.Err + var err *model.AppError + if user, err = app.GetUser(c.Session.UserId); err != nil { + c.Err = err return - } else { - user = result.Data.(*model.User) } mfaInterface := einterfaces.GetMfaInterface() @@ -2077,13 +1647,15 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) { go func() { var user *model.User - if result := <-app.Srv.Store.User().Get(c.Session.UserId); result.Err != nil { - l4g.Warn(result.Err) - } else { - user = result.Data.(*model.User) + var err *model.AppError + if user, err = app.GetUser(c.Session.UserId); err != nil { + l4g.Warn(err.Error()) + return } - sendMfaChangeEmail(c, user.Email, c.GetSiteURL(), activate) + if err := app.SendMfaChangeEmail(user.Email, activate, user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } }() rdata := map[string]string{} @@ -2107,20 +1679,11 @@ func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) { return } - // we don't need to worry about contacting the ldap server to get this user because - // only users already in the system could have MFA enabled - uchan := app.Srv.Store.User().GetForLogin( - loginId, - *utils.Cfg.EmailSettings.EnableSignInWithUsername, - *utils.Cfg.EmailSettings.EnableSignInWithEmail, - *utils.Cfg.LdapSettings.Enable, - ) - rdata := map[string]string{} - if result := <-uchan; result.Err != nil { + if user, err := app.GetUserForLogin(loginId, false); err != nil { rdata["mfa_required"] = "false" } else { - rdata["mfa_required"] = strconv.FormatBool(result.Data.(*model.User).MfaActive) + rdata["mfa_required"] = strconv.FormatBool(user.MfaActive) } w.Write([]byte(model.MapToJson(rdata))) } @@ -2214,8 +1777,16 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) { } break case model.OAUTH_ACTION_EMAIL_TO_SSO: - RevokeAllSession(c, user.Id) - go sendSignInChangeEmail(c, user.Email, c.GetSiteURL(), strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO") + if err := app.RevokeAllSessions(user.Id); err != nil { + c.Err = err + return + } + c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") + go func() { + if err := app.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.GetSiteURL()); err != nil { + l4g.Error(err.Error()) + } + }() break } doLogin(c, w, r, user, "") @@ -2307,27 +1878,26 @@ func searchUsers(c *Context, w http.ResponseWriter, r *http.Request) { c.Err = nil } - var uchan store.StoreChannel + var profiles []*model.User + var err *model.AppError if props.InChannelId != "" { - uchan = app.Srv.Store.User().SearchInChannel(props.InChannelId, props.Term, searchOptions) + profiles, err = app.SearchUsersInChannel(props.InChannelId, props.Term, searchOptions) } else if props.NotInChannelId != "" { - uchan = app.Srv.Store.User().SearchNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions) + profiles, err = app.SearchUsersNotInChannel(props.TeamId, props.NotInChannelId, props.Term, searchOptions) } else { - uchan = app.Srv.Store.User().Search(props.TeamId, props.Term, searchOptions) + profiles, err = app.SearchUsersInTeam(props.TeamId, props.Term, searchOptions) } - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if err != nil { + c.Err = err return - } else { - profiles := result.Data.([]*model.User) - - for _, p := range profiles { - sanitizeProfile(c, p) - } + } - w.Write([]byte(model.UserListToJson(profiles))) + for _, p := range profiles { + sanitizeProfile(c, p) } + + w.Write([]byte(model.UserListToJson(profiles))) } func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) { @@ -2338,12 +1908,10 @@ func getProfilesByIds(c *Context, w http.ResponseWriter, r *http.Request) { return } - if result := <-app.Srv.Store.User().GetProfileByIds(userIds, true); result.Err != nil { - c.Err = result.Err + if profiles, err := app.GetUsersByIds(userIds); err != nil { + c.Err = err return } else { - profiles := result.Data.(map[string]*model.User) - for _, p := range profiles { sanitizeProfile(c, p) } @@ -2379,35 +1947,18 @@ func autocompleteUsersInChannel(c *Context, w http.ResponseWriter, r *http.Reque searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } - uchan := app.Srv.Store.User().SearchInChannel(channelId, term, searchOptions) - nuchan := app.Srv.Store.User().SearchNotInChannel(teamId, channelId, term, searchOptions) - - autocomplete := &model.UserAutocompleteInChannel{} - - if result := <-uchan; result.Err != nil { - c.Err = result.Err + autocomplete, err := app.AutocompleteUsersInChannel(teamId, channelId, term, searchOptions) + if err != nil { + c.Err = err return - } else { - profiles := result.Data.([]*model.User) - - for _, p := range profiles { - sanitizeProfile(c, p) - } - - autocomplete.InChannel = profiles } - if result := <-nuchan; result.Err != nil { - c.Err = result.Err - return - } else { - profiles := result.Data.([]*model.User) - - for _, p := range profiles { - sanitizeProfile(c, p) - } + for _, p := range autocomplete.InChannel { + sanitizeProfile(c, p) + } - autocomplete.OutOfChannel = profiles + for _, p := range autocomplete.OutOfChannel { + sanitizeProfile(c, p) } w.Write([]byte(autocomplete.ToJson())) @@ -2435,21 +1986,14 @@ func autocompleteUsersInTeam(c *Context, w http.ResponseWriter, r *http.Request) searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } - uchan := app.Srv.Store.User().Search(teamId, term, searchOptions) - - autocomplete := &model.UserAutocompleteInTeam{} - - if result := <-uchan; result.Err != nil { - c.Err = result.Err + autocomplete, err := app.AutocompleteUsersInTeam(teamId, term, searchOptions) + if err != nil { + c.Err = err return - } else { - profiles := result.Data.([]*model.User) - - for _, p := range profiles { - sanitizeProfile(c, p) - } + } - autocomplete.InTeam = profiles + for _, p := range autocomplete.InTeam { + sanitizeProfile(c, p) } w.Write([]byte(autocomplete.ToJson())) @@ -2468,19 +2012,16 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) { searchOptions[store.USER_SEARCH_OPTION_NAMES_ONLY] = true } - uchan := app.Srv.Store.User().Search("", term, searchOptions) - var profiles []*model.User + var err *model.AppError - if result := <-uchan; result.Err != nil { - c.Err = result.Err + if profiles, err = app.SearchUsersInTeam("", term, searchOptions); err != nil { + c.Err = err return - } else { - profiles = result.Data.([]*model.User) + } - for _, p := range profiles { - sanitizeProfile(c, p) - } + for _, p := range profiles { + sanitizeProfile(c, p) } w.Write([]byte(model.UserListToJson(profiles))) diff --git a/api/user_test.go b/api/user_test.go index 56064232b..96d1fd3d0 100644 --- a/api/user_test.go +++ b/api/user_test.go @@ -508,7 +508,7 @@ func TestGetUser(t *testing.T) { t.Fatal("shouldn't have accss") } - UpdateUserRoles(ruser.Data.(*model.User), model.ROLE_SYSTEM_ADMIN.Id) + app.UpdateUserRoles(ruser.Data.(*model.User).Id, model.ROLE_SYSTEM_ADMIN.Id) Client.Login(user.Email, "passwd1") @@ -1226,7 +1226,7 @@ func TestUserPermDelete(t *testing.T) { c.RequestId = model.NewId() c.IpAddress = "test" - err := PermanentDeleteUser(user1) + err := app.PermanentDeleteUser(user1) if err != nil { t.Fatal(err) } 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 +} diff --git a/cmd/platform/channel.go b/cmd/platform/channel.go index 2a10516af..fd2b097af 100644 --- a/cmd/platform/channel.go +++ b/cmd/platform/channel.go @@ -5,7 +5,6 @@ package main import ( "errors" - "github.com/mattermost/platform/api" "github.com/mattermost/platform/app" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" @@ -166,7 +165,7 @@ func removeUserFromChannel(channel *model.Channel, user *model.User, userArg str CommandPrintErrorln("Can't find user '" + userArg + "'") return } - if err := api.RemoveUserFromChannel(user.Id, "", channel); err != nil { + if err := app.RemoveUserFromChannel(user.Id, "", channel); err != nil { CommandPrintErrorln("Unable to remove '" + userArg + "' from " + channel.Name + ". Error: " + err.Error()) } } diff --git a/cmd/platform/oldcommands.go b/cmd/platform/oldcommands.go index faf086cb7..1ff65130c 100644 --- a/cmd/platform/oldcommands.go +++ b/cmd/platform/oldcommands.go @@ -319,8 +319,7 @@ func cmdInviteUser() { } invites := []string{flagEmail} - c := getMockContext() - api.InviteMembers(team, user.GetDisplayName(), invites, c.GetSiteURL()) + app.SendInviteEmails(team, user.GetDisplayName(), invites, *utils.Cfg.ServiceSettings.SiteURL) os.Exit(0) } @@ -369,7 +368,7 @@ func cmdAssignRole() { } if !user.IsInRole(flagRole) { - api.UpdateUserRoles(user, flagRole) + app.UpdateUserRoles(user.Id, flagRole) } os.Exit(0) @@ -547,7 +546,7 @@ func cmdLeaveChannel() { channel = result.Data.(*model.Channel) } - err := api.RemoveUserFromChannel(user.Id, user.Id, channel) + err := app.RemoveUserFromChannel(user.Id, user.Id, channel) if err != nil { l4g.Error("%v", err) flushLogAndExit(1) @@ -712,7 +711,7 @@ func cmdLeaveTeam() { user = result.Data.(*model.User) } - err := api.LeaveTeam(team, user) + err := app.LeaveTeam(team, user) if err != nil { l4g.Error("%v", err) @@ -823,7 +822,7 @@ func cmdPermDeleteUser() { flushLogAndExit(1) } - if err := api.PermanentDeleteUser(user); err != nil { + if err := app.PermanentDeleteUser(user); err != nil { l4g.Error("%v", err) flushLogAndExit(1) } else { @@ -866,7 +865,7 @@ func cmdPermDeleteTeam() { flushLogAndExit(1) } - if err := api.PermanentDeleteTeam(team); err != nil { + if err := app.PermanentDeleteTeam(team); err != nil { l4g.Error("%v", err) flushLogAndExit(1) } else { @@ -896,7 +895,7 @@ func cmdPermDeleteAllUsers() { flushLogAndExit(1) } - if err := api.PermanentDeleteAllUsers(); err != nil { + if err := app.PermanentDeleteAllUsers(); err != nil { l4g.Error("%v", err) flushLogAndExit(1) } else { @@ -1033,7 +1032,7 @@ func cmdActivateUser() { l4g.Error("%v", utils.T("api.user.update_active.no_deactivate_ldap.app_error")) } - if _, err := api.UpdateActive(user, !flagUserSetInactive); err != nil { + if _, err := app.UpdateActive(user, !flagUserSetInactive); err != nil { l4g.Error("%v", err) } diff --git a/cmd/platform/roles.go b/cmd/platform/roles.go index 7b635c5a3..a65eb2bd6 100644 --- a/cmd/platform/roles.go +++ b/cmd/platform/roles.go @@ -5,7 +5,7 @@ package main import ( "errors" - "github.com/mattermost/platform/api" + "github.com/mattermost/platform/app" "github.com/spf13/cobra" ) @@ -49,7 +49,7 @@ func makeSystemAdminCmdF(cmd *cobra.Command, args []string) error { return errors.New("Unable to find user '" + args[i] + "'") } - if _, err := api.UpdateUserRoles(user, "system_admin system_user"); err != nil { + if _, err := app.UpdateUserRoles(user.Id, "system_admin system_user"); err != nil { return err } } @@ -69,7 +69,7 @@ func makeMemberCmdF(cmd *cobra.Command, args []string) error { return errors.New("Unable to find user '" + args[i] + "'") } - if _, err := api.UpdateUserRoles(user, "system_user"); err != nil { + if _, err := app.UpdateUserRoles(user.Id, "system_user"); err != nil { return err } } diff --git a/cmd/platform/team.go b/cmd/platform/team.go index 43d0b2582..1dc5d46eb 100644 --- a/cmd/platform/team.go +++ b/cmd/platform/team.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" - "github.com/mattermost/platform/api" "github.com/mattermost/platform/app" "github.com/mattermost/platform/model" "github.com/spf13/cobra" @@ -125,7 +124,7 @@ func removeUserFromTeam(team *model.Team, user *model.User, userArg string) { CommandPrintErrorln("Can't find user '" + userArg + "'") return } - if err := api.LeaveTeam(team, user); err != nil { + if err := app.LeaveTeam(team, user); err != nil { CommandPrintErrorln("Unable to remove '" + userArg + "' from " + team.Name + ". Error: " + err.Error()) } } @@ -200,5 +199,5 @@ func deleteTeamsCmdF(cmd *cobra.Command, args []string) error { } func deleteTeam(team *model.Team) *model.AppError { - return api.PermanentDeleteTeam(team) + return app.PermanentDeleteTeam(team) } diff --git a/cmd/platform/user.go b/cmd/platform/user.go index 4fa4cd36e..fa93e418c 100644 --- a/cmd/platform/user.go +++ b/cmd/platform/user.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" - "github.com/mattermost/platform/api" "github.com/mattermost/platform/app" "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" @@ -175,7 +174,7 @@ func changeUserActiveStatus(user *model.User, userArg string, activate bool) { CommandPrintErrorln(utils.T("api.user.update_active.no_deactivate_ldap.app_error")) return } - if _, err := api.UpdateActive(user, activate); err != nil { + if _, err := app.UpdateActive(user, activate); err != nil { CommandPrintErrorln("Unable to change activation status of user: " + userArg) } } @@ -227,7 +226,7 @@ func userCreateCmdF(cmd *cobra.Command, args []string) error { } if system_admin { - api.UpdateUserRoles(ruser, "system_user system_admin") + app.UpdateUserRoles(ruser.Id, "system_user system_admin") } CommandPrettyPrintln("Created User") @@ -262,7 +261,7 @@ func inviteUser(email string, team *model.Team, teamArg string) { CommandPrintErrorln("Can't find team '" + teamArg + "'") return } - api.InviteMembers(team, "Administrator", invites, *utils.Cfg.ServiceSettings.SiteURL) + app.SendInviteEmails(team, "Administrator", invites, *utils.Cfg.ServiceSettings.SiteURL) CommandPrettyPrintln("Invites may or may not have been sent.") } @@ -335,7 +334,7 @@ func deleteUserCmdF(cmd *cobra.Command, args []string) error { return errors.New("Unable to find user '" + args[i] + "'") } - if err := api.PermanentDeleteUser(user); err != nil { + if err := app.PermanentDeleteUser(user); err != nil { return err } } @@ -365,7 +364,7 @@ func deleteAllUsersCommandF(cmd *cobra.Command, args []string) error { } } - if err := api.PermanentDeleteAllUsers(); err != nil { + if err := app.PermanentDeleteAllUsers(); err != nil { return err } else { CommandPrettyPrintln("Sucsessfull. All users deleted.") diff --git a/einterfaces/cluster.go b/einterfaces/cluster.go index 0d7bf7e86..6cf57308c 100644 --- a/einterfaces/cluster.go +++ b/einterfaces/cluster.go @@ -12,7 +12,7 @@ type ClusterInterface interface { StopInterNodeCommunication() GetClusterInfos() []*model.ClusterInfo GetClusterStats() ([]*model.ClusterStats, *model.AppError) - RemoveAllSessionsForUserId(userId string) + ClearSessionCacheForUser(userId string) InvalidateCacheForUser(userId string) InvalidateCacheForChannel(channelId string) InvalidateCacheForChannelPosts(channelId string) diff --git a/i18n/en.json b/i18n/en.json index 79bf1b98e..cbfe0c807 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2525,19 +2525,19 @@ }, { "id": "api.user.send_email_change_email_and_forget.error", - "translation": "Failed to send email change notification email successfully err=%v" + "translation": "Failed to send email change notification email successfully" }, { "id": "api.user.send_email_change_username_and_forget.error", - "translation": "Failed to send username change notification email successfully err=%v" + "translation": "Failed to send username change notification email successfully" }, { "id": "api.user.send_email_change_verify_email_and_forget.error", - "translation": "Failed to send email change verification email successfully err=%v" + "translation": "Failed to send email change verification email successfully" }, { "id": "api.user.send_password_change_email_and_forget.error", - "translation": "Failed to send update password email successfully err=%v" + "translation": "Failed to send update password email successfully" }, { "id": "api.user.send_password_reset.find.app_error", @@ -2553,15 +2553,15 @@ }, { "id": "api.user.send_sign_in_change_email_and_forget.error", - "translation": "Failed to send update password email successfully err=%v" + "translation": "Failed to send update password email successfully" }, { "id": "api.user.send_verify_email_and_forget.failed.error", - "translation": "Failed to send verification email successfully err=%v" + "translation": "Failed to send verification email successfully" }, { "id": "api.user.send_welcome_email_and_forget.failed.error", - "translation": "Failed to send welcome email successfully err=%v" + "translation": "Failed to send welcome email successfully" }, { "id": "api.user.update_active.no_deactivate_ldap.app_error", diff --git a/model/file.go b/model/file.go index c218c4246..20f6236de 100644 --- a/model/file.go +++ b/model/file.go @@ -8,6 +8,10 @@ import ( "io" ) +const ( + MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image +) + var ( IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"} IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"} diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index 66562ae67..6ac69df5a 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -702,7 +702,7 @@ func (s SqlChannelStore) GetMember(channelId string, userId string) StoreChannel result.Err = model.NewLocAppError("SqlChannelStore.GetMember", "store.sql_channel.get_member.app_error", nil, "channel_id="+channelId+"user_id="+userId+","+err.Error()) } } else { - result.Data = member + result.Data = &member } storeChannel <- result @@ -1361,7 +1361,7 @@ func (s SqlChannelStore) GetMembersByIds(channelId string, userIds []string) Sto if _, err := s.GetReplica().Select(&members, "SELECT * FROM ChannelMembers WHERE ChannelId = :ChannelId AND UserId IN ("+idQuery+")", props); err != nil { result.Err = model.NewLocAppError("SqlChannelStore.GetMembersByIds", "store.sql_channel.get_members_by_ids.app_error", nil, "channelId="+channelId+" "+err.Error()) } else { - result.Data = members + result.Data = &members } storeChannel <- result diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go index 6186a242c..38446bf94 100644 --- a/store/sql_channel_store_test.go +++ b/store/sql_channel_store_test.go @@ -446,7 +446,7 @@ func TestChannelMemberStore(t *testing.T) { t.Fatal("Member update time incorrect on delete") } - member := (<-store.Channel().GetMember(o1.ChannelId, o1.UserId)).Data.(model.ChannelMember) + member := (<-store.Channel().GetMember(o1.ChannelId, o1.UserId)).Data.(*model.ChannelMember) if member.ChannelId != o1.ChannelId { t.Fatal("should have go member") } @@ -918,7 +918,7 @@ func TestGetMember(t *testing.T) { if result := <-store.Channel().GetMember(c1.Id, userId); result.Err != nil { t.Fatal("shouldn't have errored when getting member", result.Err) - } else if member := result.Data.(model.ChannelMember); member.ChannelId != c1.Id { + } else if member := result.Data.(*model.ChannelMember); member.ChannelId != c1.Id { t.Fatal("should've gotten member of channel 1") } else if member.UserId != userId { t.Fatal("should've gotten member for user") @@ -926,7 +926,7 @@ func TestGetMember(t *testing.T) { if result := <-store.Channel().GetMember(c2.Id, userId); result.Err != nil { t.Fatal("shouldn't have errored when getting member", result.Err) - } else if member := result.Data.(model.ChannelMember); member.ChannelId != c2.Id { + } else if member := result.Data.(*model.ChannelMember); member.ChannelId != c2.Id { t.Fatal("should've gotten member of channel 2") } else if member.UserId != userId { t.Fatal("should've gotten member for user") @@ -1313,7 +1313,7 @@ func TestChannelStoreGetMembersByIds(t *testing.T) { if r := <-store.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId}); r.Err != nil { t.Fatal(r.Err) } else { - rm1 := r.Data.(model.ChannelMembers)[0] + rm1 := (*r.Data.(*model.ChannelMembers))[0] if rm1.ChannelId != m1.ChannelId { t.Fatal("bad team id") @@ -1330,7 +1330,7 @@ func TestChannelStoreGetMembersByIds(t *testing.T) { if r := <-store.Channel().GetMembersByIds(m1.ChannelId, []string{m1.UserId, m2.UserId, model.NewId()}); r.Err != nil { t.Fatal(r.Err) } else { - rm := r.Data.(model.ChannelMembers) + rm := (*r.Data.(*model.ChannelMembers)) if len(rm) != 2 { t.Fatal("return wrong number of results") diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 023ce8a5a..b0af895c5 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -301,7 +301,7 @@ func (s SqlTeamStore) GetAllTeamListing() StoreChannel { var data []*model.Team if _, err := s.GetReplica().Select(&data, query); err != nil { - result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeams", "store.sql_team.get_all_team_listing.app_error", nil, err.Error()) + result.Err = model.NewLocAppError("SqlTeamStore.GetAllTeamListing", "store.sql_team.get_all_team_listing.app_error", nil, err.Error()) } for _, team := range data { @@ -438,7 +438,7 @@ func (s SqlTeamStore) GetMember(teamId string, userId string) StoreChannel { result.Err = model.NewLocAppError("SqlTeamStore.GetMember", "store.sql_team.get_member.app_error", nil, "teamId="+teamId+" userId="+userId+" "+err.Error()) } } else { - result.Data = member + result.Data = &member } storeChannel <- result diff --git a/store/sql_team_store_test.go b/store/sql_team_store_test.go index 8a302a9e2..0ea499d7e 100644 --- a/store/sql_team_store_test.go +++ b/store/sql_team_store_test.go @@ -415,7 +415,7 @@ func TestGetTeamMember(t *testing.T) { if r := <-store.Team().GetMember(m1.TeamId, m1.UserId); r.Err != nil { t.Fatal(r.Err) } else { - rm1 := r.Data.(model.TeamMember) + rm1 := r.Data.(*model.TeamMember) if rm1.TeamId != m1.TeamId { t.Fatal("bad team id") diff --git a/utils/config.go b/utils/config.go index 3825d397d..72c30c01b 100644 --- a/utils/config.go +++ b/utils/config.go @@ -39,7 +39,7 @@ func GetSiteURL() string { } func SetSiteURL(url string) { - siteURL = url + siteURL = strings.TrimRight(url, "/") } func FindConfigFile(fileName string) string { diff --git a/web/web_test.go b/web/web_test.go index 80a0282b4..11273c5dd 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -209,7 +209,7 @@ func TestIncomingWebhook(t *testing.T) { store.Must(app.Srv.Store.User().VerifyEmail(user.Id)) app.JoinUserToTeam(team, user) - api.UpdateUserRoles(user, model.ROLE_SYSTEM_ADMIN.Id) + app.UpdateUserRoles(user.Id, model.ROLE_SYSTEM_ADMIN.Id) ApiClient.Login(user.Email, "passwd1") ApiClient.SetTeamId(team.Id) |