// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. package api import ( l4g "code.google.com/p/log4go" "fmt" "github.com/gorilla/mux" "github.com/mattermost/platform/model" "github.com/mattermost/platform/store" "net/http" "strings" ) func InitChannel(r *mux.Router) { l4g.Debug("Initializing channel api routes") sr := r.PathPrefix("/channels").Subrouter() sr.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET") sr.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET") sr.Handle("/create", ApiUserRequired(createChannel)).Methods("POST") sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST") sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST") sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST") sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET") sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(joinChannel)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/leave", ApiUserRequired(leaveChannel)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/delete", ApiUserRequired(deleteChannel)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/add", ApiUserRequired(addChannelMember)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/remove", ApiUserRequired(removeChannelMember)).Methods("POST") sr.Handle("/{id:[A-Za-z0-9]+}/update_last_viewed_at", ApiUserRequired(updateLastViewedAt)).Methods("POST") } func createChannel(c *Context, w http.ResponseWriter, r *http.Request) { channel := model.ChannelFromJson(r.Body) if channel == nil { c.SetInvalidParam("createChannel", "channel") return } if !c.HasPermissionsToTeam(channel.TeamId, "createChannel") { return } if channel.Type == model.CHANNEL_DIRECT { c.Err = model.NewAppError("createDirectChannel", "Must use createDirectChannel api service for direct message channel creation", "") return } if strings.Index(channel.Name, "__") > 0 { c.Err = model.NewAppError("createDirectChannel", "Invalid character '__' in channel name for non-direct channel", "") return } if sc, err := CreateChannel(c, channel, r.URL.Path, true); err != nil { c.Err = err return } else { w.Write([]byte(sc.ToJson())) } } func CreateChannel(c *Context, channel *model.Channel, path string, addMember bool) (*model.Channel, *model.AppError) { if result := <-Srv.Store.Channel().Save(channel); result.Err != nil { return nil, result.Err } else { sc := result.Data.(*model.Channel) if addMember { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: c.Session.UserId, Roles: model.CHANNEL_ROLE_ADMIN, NotifyLevel: model.CHANNEL_NOTIFY_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err } } c.LogAudit("name=" + channel.Name) return sc, nil } } func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapFromJson(r.Body) userId := data["user_id"] if len(userId) != 26 { c.SetInvalidParam("createDirectChannel", "user_id") return } if !c.HasPermissionsToTeam(c.Session.TeamId, "createDirectChannel") { return } if sc, err := CreateDirectChannel(c, userId, r.URL.Path); err != nil { c.Err = err return } else { w.Write([]byte(sc.ToJson())) } } func CreateDirectChannel(c *Context, otherUserId string, path string) (*model.Channel, *model.AppError) { if len(otherUserId) != 26 { return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId) } uc := Srv.Store.User().Get(otherUserId) channel := new(model.Channel) channel.DisplayName = "" if otherUserId > c.Session.UserId { channel.Name = c.Session.UserId + "__" + otherUserId } else { channel.Name = otherUserId + "__" + c.Session.UserId } channel.TeamId = c.Session.TeamId channel.Description = "" channel.Type = model.CHANNEL_DIRECT if uresult := <-uc; uresult.Err != nil { return nil, model.NewAppError("CreateDirectChannel", "Invalid other user id ", otherUserId) } if sc, err := CreateChannel(c, channel, path, true); err != nil { return nil, err } else { cm := &model.ChannelMember{ChannelId: sc.Id, UserId: otherUserId, Roles: "", NotifyLevel: model.CHANNEL_NOTIFY_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { return nil, cmresult.Err } return sc, nil } } func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) { channel := model.ChannelFromJson(r.Body) if channel == nil { c.SetInvalidParam("updateChannel", "channel") return } sc := Srv.Store.Channel().Get(channel.Id) cmc := Srv.Store.Channel().GetMember(channel.Id, c.Session.UserId) if cresult := <-sc; cresult.Err != nil { c.Err = cresult.Err return } else if cmcresult := <-cmc; cmcresult.Err != nil { c.Err = cmcresult.Err return } else { oldChannel := cresult.Data.(*model.Channel) channelMember := cmcresult.Data.(model.ChannelMember) if !c.HasPermissionsToTeam(oldChannel.TeamId, "updateChannel") { return } if !strings.Contains(channelMember.Roles, model.CHANNEL_ROLE_ADMIN) && !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { c.Err = model.NewAppError("updateChannel", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden return } if oldChannel.DeleteAt > 0 { c.Err = model.NewAppError("updateChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if oldChannel.Name == model.DEFAULT_CHANNEL { c.Err = model.NewAppError("updateChannel", "Cannot update the default channel "+model.DEFAULT_CHANNEL, "") c.Err.StatusCode = http.StatusForbidden return } oldChannel.Description = channel.Description if len(channel.DisplayName) > 0 { oldChannel.DisplayName = channel.DisplayName } if len(channel.Name) > 0 { oldChannel.Name = channel.Name } if len(channel.Type) > 0 { oldChannel.Type = channel.Type } if ucresult := <-Srv.Store.Channel().Update(oldChannel); ucresult.Err != nil { c.Err = ucresult.Err return } else { c.LogAudit("name=" + channel.Name) w.Write([]byte(oldChannel.ToJson())) } } } func updateChannelDesc(c *Context, w http.ResponseWriter, r *http.Request) { props := model.MapFromJson(r.Body) channelId := props["channel_id"] if len(channelId) != 26 { c.SetInvalidParam("updateChannelDesc", "channel_id") return } channelDesc := props["channel_description"] if len(channelDesc) > 1024 { c.SetInvalidParam("updateChannelDesc", "channel_description") return } sc := Srv.Store.Channel().Get(channelId) cmc := Srv.Store.Channel().GetMember(channelId, c.Session.UserId) 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) // Don't need to do anything channel member, just wanted to confirm it exists if !c.HasPermissionsToTeam(channel.TeamId, "updateChannelDesc") { return } channel.Description = channelDesc if ucresult := <-Srv.Store.Channel().Update(channel); ucresult.Err != nil { c.Err = ucresult.Err return } else { c.LogAudit("name=" + channel.Name) w.Write([]byte(channel.ToJson())) } } } func getChannels(c *Context, w http.ResponseWriter, r *http.Request) { // user is already in the newtork if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { if result.Err.Message == "No channels were found" { // lets make sure the user is valid if result := <-Srv.Store.User().Get(c.Session.UserId); result.Err != nil { c.Err = result.Err c.RemoveSessionCookie(w) l4g.Error("Error in getting users profile for id=%v forcing logout", c.Session.UserId) return } } c.Err = result.Err return } else if HandleEtag(result.Data.(*model.ChannelList).Etag(), w, r) { return } else { data := result.Data.(*model.ChannelList) w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) w.Write([]byte(data.ToJson())) } } func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) { // user is already in the newtork if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil { c.Err = result.Err return } else if HandleEtag(result.Data.(*model.ChannelList).Etag(), w, r) { return } else { data := result.Data.(*model.ChannelList) w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag()) w.Write([]byte(data.ToJson())) } } func joinChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) channelId := params["id"] JoinChannel(c, channelId, r.URL.Path) if c.Err != nil { return } result := make(map[string]string) result["id"] = channelId w.Write([]byte(model.MapToJson(result))) } func JoinChannel(c *Context, channelId string, path string) { sc := Srv.Store.Channel().Get(channelId) uc := Srv.Store.User().Get(c.Session.UserId) if cresult := <-sc; cresult.Err != nil { c.Err = cresult.Err return } else if uresult := <-uc; uresult.Err != nil { c.Err = uresult.Err return } else { channel := cresult.Data.(*model.Channel) user := uresult.Data.(*model.User) if !c.HasPermissionsToTeam(channel.TeamId, "joinChannel") { return } if channel.DeleteAt > 0 { c.Err = model.NewAppError("joinChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if channel.Type == model.CHANNEL_OPEN { cm := &model.ChannelMember{ChannelId: channel.Id, UserId: c.Session.UserId, NotifyLevel: model.CHANNEL_NOTIFY_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { c.Err = cmresult.Err return } post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf( `User %v has joined this channel.`, user.Username)} if _, err := CreatePost(c, post, false); err != nil { l4g.Error("Failed to post join message %v", err) c.Err = model.NewAppError("joinChannel", "Failed to send join request", "") return } } else { c.Err = model.NewAppError("joinChannel", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden return } } } func leaveChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] sc := Srv.Store.Channel().Get(id) uc := Srv.Store.User().Get(c.Session.UserId) if cresult := <-sc; cresult.Err != nil { c.Err = cresult.Err return } else if uresult := <-uc; uresult.Err != nil { c.Err = cresult.Err return } else { channel := cresult.Data.(*model.Channel) user := uresult.Data.(*model.User) if !c.HasPermissionsToTeam(channel.TeamId, "leaveChannel") { return } if channel.DeleteAt > 0 { c.Err = model.NewAppError("leaveChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if channel.Type == model.CHANNEL_DIRECT { c.Err = model.NewAppError("leaveChannel", "Cannot leave a direct message channel", "") c.Err.StatusCode = http.StatusForbidden return } if channel.Name == model.DEFAULT_CHANNEL { c.Err = model.NewAppError("leaveChannel", "Cannot leave the default channel "+model.DEFAULT_CHANNEL, "") c.Err.StatusCode = http.StatusForbidden return } if cmresult := <-Srv.Store.Channel().RemoveMember(channel.Id, c.Session.UserId); cmresult.Err != nil { c.Err = cmresult.Err return } post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf( `%v has left the channel.`, user.Username)} if _, err := CreatePost(c, post, false); err != nil { l4g.Error("Failed to post leave message %v", err) c.Err = model.NewAppError("leaveChannel", "Failed to send leave message", "") return } result := make(map[string]string) result["id"] = channel.Id w.Write([]byte(model.MapToJson(result))) } } func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] sc := Srv.Store.Channel().Get(id) scm := Srv.Store.Channel().GetMember(id, c.Session.UserId) uc := Srv.Store.User().Get(c.Session.UserId) 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 { channel := cresult.Data.(*model.Channel) user := uresult.Data.(*model.User) channelMember := scmresult.Data.(model.ChannelMember) if !c.HasPermissionsToTeam(channel.TeamId, "deleteChannel") { return } if !strings.Contains(channelMember.Roles, model.CHANNEL_ROLE_ADMIN) && !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { c.Err = model.NewAppError("deleteChannel", "You do not have the appropriate permissions", "") c.Err.StatusCode = http.StatusForbidden return } if channel.DeleteAt > 0 { c.Err = model.NewAppError("deleteChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if channel.Name == model.DEFAULT_CHANNEL { c.Err = model.NewAppError("deleteChannel", "Cannot delete the default channel "+model.DEFAULT_CHANNEL, "") c.Err.StatusCode = http.StatusForbidden return } if dresult := <-Srv.Store.Channel().Delete(channel.Id, model.GetMillis()); dresult.Err != nil { c.Err = dresult.Err return } c.LogAudit("name=" + channel.Name) post := &model.Post{ChannelId: channel.Id, Message: fmt.Sprintf( `%v has archived the channel.`, user.Username)} if _, err := CreatePost(c, post, false); err != nil { l4g.Error("Failed to post archive message %v", err) c.Err = model.NewAppError("deleteChannel", "Failed to send archive message", "") return } result := make(map[string]string) result["id"] = channel.Id w.Write([]byte(model.MapToJson(result))) } } func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] Srv.Store.Channel().UpdateLastViewedAt(id, c.Session.UserId) message := model.NewMessage(c.Session.TeamId, id, c.Session.UserId, model.ACTION_VIEWED) message.Add("channel_id", id) store.PublishAndForget(message) result := make(map[string]string) result["id"] = id w.Write([]byte(model.MapToJson(result))) } func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] sc := Srv.Store.Channel().Get(id) scm := Srv.Store.Channel().GetMember(id, c.Session.UserId) ecm := Srv.Store.Channel().GetExtraMembers(id, 20) if cresult := <-sc; cresult.Err != nil { c.Err = cresult.Err return } else if cmresult := <-scm; cmresult.Err != nil { c.Err = cmresult.Err return } else if ecmresult := <-ecm; ecmresult.Err != nil { c.Err = ecmresult.Err return } else { channel := cresult.Data.(*model.Channel) member := cmresult.Data.(model.ChannelMember) extraMembers := ecmresult.Data.([]model.ExtraMember) if !c.HasPermissionsToTeam(channel.TeamId, "getChannelExtraInfo") { return } if !c.HasPermissionsToUser(member.UserId, "getChannelExtraInfo") { return } if channel.DeleteAt > 0 { c.Err = model.NewAppError("getChannelExtraInfo", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } data := model.ChannelExtra{Id: channel.Id, Members: extraMembers} w.Header().Set("Expires", "-1") w.Write([]byte(data.ToJson())) } } func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] data := model.MapFromJson(r.Body) userId := data["user_id"] if len(userId) != 26 { c.SetInvalidParam("addChannelMember", "user_id") return } cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId) sc := Srv.Store.Channel().Get(id) ouc := Srv.Store.User().Get(c.Session.UserId) nuc := Srv.Store.User().Get(userId) // Only need to be a member of the channel to add a new member if !c.HasPermissionsToChannel(cchan, "addChannelMember") { return } if nresult := <-nuc; nresult.Err != nil { c.Err = model.NewAppError("addChannelMember", "Failed to find user to be added", "") return } else if cresult := <-sc; cresult.Err != nil { c.Err = model.NewAppError("addChannelMember", "Failed to find channel", "") return } else { channel := cresult.Data.(*model.Channel) nUser := nresult.Data.(*model.User) if channel.DeleteAt > 0 { c.Err = model.NewAppError("updateChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if oresult := <-ouc; oresult.Err != nil { c.Err = model.NewAppError("addChannelMember", "Failed to find user doing the adding", "") return } else { oUser := oresult.Data.(*model.User) cm := &model.ChannelMember{ChannelId: channel.Id, UserId: userId, NotifyLevel: model.CHANNEL_NOTIFY_ALL} if cmresult := <-Srv.Store.Channel().SaveMember(cm); cmresult.Err != nil { l4g.Error("Failed to add member user_id=%v channel_id=%v err=%v", userId, id, cmresult.Err) c.Err = model.NewAppError("addChannelMember", "Failed to add user to channel", "") return } post := &model.Post{ChannelId: id, Message: fmt.Sprintf( `%v added to the channel by %v`, nUser.Username, oUser.Username)} if _, err := CreatePost(c, post, false); err != nil { l4g.Error("Failed to post add message %v", err) c.Err = model.NewAppError("addChannelMember", "Failed to add member to channel", "") return } c.LogAudit("name=" + channel.Name + " user_id=" + userId) <-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id) w.Write([]byte(cm.ToJson())) } } } func removeChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] data := model.MapFromJson(r.Body) userId := data["user_id"] if len(userId) != 26 { c.SetInvalidParam("addChannelMember", "user_id") return } sc := Srv.Store.Channel().Get(id) cmc := Srv.Store.Channel().GetMember(id, c.Session.UserId) 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) channelMember := cmcresult.Data.(model.ChannelMember) if !c.HasPermissionsToTeam(channel.TeamId, "removeChannelMember") { return } if !strings.Contains(channelMember.Roles, model.CHANNEL_ROLE_ADMIN) && !strings.Contains(c.Session.Roles, model.ROLE_ADMIN) { c.Err = model.NewAppError("updateChannel", "You do not have the appropriate permissions ", "") c.Err.StatusCode = http.StatusForbidden return } if channel.DeleteAt > 0 { c.Err = model.NewAppError("updateChannel", "The channel has been archived or deleted", "") c.Err.StatusCode = http.StatusBadRequest return } if cmresult := <-Srv.Store.Channel().RemoveMember(id, userId); cmresult.Err != nil { c.Err = cmresult.Err return } c.LogAudit("name=" + channel.Name + " user_id=" + userId) result := make(map[string]string) result["channel_id"] = channel.Id result["removed_user_id"] = userId w.Write([]byte(model.MapToJson(result))) } } func updateNotifyLevel(c *Context, w http.ResponseWriter, r *http.Request) { data := model.MapFromJson(r.Body) userId := data["user_id"] if len(userId) != 26 { c.SetInvalidParam("updateNotifyLevel", "user_id") return } channelId := data["channel_id"] if len(channelId) != 26 { c.SetInvalidParam("updateNotifyLevel", "channel_id") return } notifyLevel := data["notify_level"] if len(notifyLevel) == 0 || !model.IsChannelNotifyLevelValid(notifyLevel) { c.SetInvalidParam("updateNotifyLevel", "notify_level") return } cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) if !c.HasPermissionsToUser(userId, "updateNotifyLevel") { return } if !c.HasPermissionsToChannel(cchan, "updateNotifyLevel") { return } if result := <-Srv.Store.Channel().UpdateNotifyLevel(channelId, userId, notifyLevel); result.Err != nil { c.Err = result.Err return } w.Write([]byte(model.MapToJson(data))) }