// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package api4 import ( "encoding/json" "net/http" "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/mlog" ) func (api *API) InitWebhook() { api.BaseRoutes.IncomingHooks.Handle("", api.APISessionRequired(createIncomingHook)).Methods(http.MethodPost) api.BaseRoutes.IncomingHooks.Handle("", api.APISessionRequired(getIncomingHooks)).Methods(http.MethodGet) api.BaseRoutes.IncomingHook.Handle("", api.APISessionRequired(getIncomingHook)).Methods(http.MethodGet) api.BaseRoutes.IncomingHook.Handle("", api.APISessionRequired(updateIncomingHook)).Methods(http.MethodPut) api.BaseRoutes.IncomingHook.Handle("", api.APISessionRequired(deleteIncomingHook)).Methods(http.MethodDelete) api.BaseRoutes.OutgoingHooks.Handle("", api.APISessionRequired(createOutgoingHook)).Methods(http.MethodPost) api.BaseRoutes.OutgoingHooks.Handle("", api.APISessionRequired(getOutgoingHooks)).Methods(http.MethodGet) api.BaseRoutes.OutgoingHook.Handle("", api.APISessionRequired(getOutgoingHook)).Methods(http.MethodGet) api.BaseRoutes.OutgoingHook.Handle("", api.APISessionRequired(updateOutgoingHook)).Methods(http.MethodPut) api.BaseRoutes.OutgoingHook.Handle("", api.APISessionRequired(deleteOutgoingHook)).Methods(http.MethodDelete) api.BaseRoutes.OutgoingHook.Handle("/regen_token", api.APISessionRequired(regenOutgoingHookToken)).Methods(http.MethodPost) } func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { var hook model.IncomingWebhook if jsonErr := json.NewDecoder(r.Body).Decode(&hook); jsonErr != nil { c.SetInvalidParamWithErr("incoming_webhook", jsonErr) return } channel, err := c.App.GetChannel(c.AppContext, hook.ChannelId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventCreateIncomingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterAuditableToAuditRec(auditRec, "incoming_webhook", &hook) model.AddEventParameterAuditableToAuditRec(auditRec, "channel", channel) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageOwnIncomingWebhooks) { c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } if !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { c.LogAudit("fail - bad channel permissions") c.SetPermissionError(model.PermissionReadChannelContent) return } userId := c.AppContext.Session().UserId if hook.UserId != "" && hook.UserId != userId { if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageOthersIncomingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersIncomingWebhooks) return } if _, err = c.App.GetUser(hook.UserId); err != nil { c.Err = err return } userId = hook.UserId } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionBypassIncomingWebhookChannelLock) { hook.ChannelLocked = true hook.ChannelId = channel.Id } incomingHook, err := c.App.CreateIncomingWebhookForChannel(userId, channel, &hook) if err != nil { c.Err = err return } auditRec.Success() auditRec.AddEventResultState(incomingHook) auditRec.AddEventObjectType("hook") c.LogAudit("success") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(incomingHook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func updateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } var updatedHook model.IncomingWebhook if jsonErr := json.NewDecoder(r.Body).Decode(&updatedHook); jsonErr != nil { c.SetInvalidParamWithErr("incoming_webhook", jsonErr) return } // The hook being updated in the payload must be the same one as indicated in the URL. if updatedHook.Id != c.Params.HookId { c.SetInvalidParam("hook_id") return } auditRec := c.MakeAuditRecord(model.AuditEventUpdateIncomingHook, model.AuditStatusFail) model.AddEventParameterToAuditRec(auditRec, "hook_id", c.Params.HookId) model.AddEventParameterAuditableToAuditRec(auditRec, "updated_hook", &updatedHook) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") oldHook, err := c.App.GetIncomingWebhook(c.Params.HookId) if err != nil { c.Err = err return } auditRec.AddEventPriorState(oldHook) auditRec.AddEventObjectType("incoming_webhook") if updatedHook.TeamId == "" { updatedHook.TeamId = oldHook.TeamId } if updatedHook.TeamId != oldHook.TeamId { c.Err = model.NewAppError("updateIncomingHook", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.AppContext.Session().UserId, http.StatusBadRequest) return } channel, err := c.App.GetChannel(c.AppContext, updatedHook.ChannelId) if err != nil { c.Err = err return } auditRec.AddMeta("channel_id", channel.Id) auditRec.AddMeta("channel_name", channel.Name) if channel.TeamId != updatedHook.TeamId { c.SetInvalidParam("channel_id") return } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageOwnIncomingWebhooks) { c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } if c.AppContext.Session().UserId != oldHook.UserId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionManageOthersIncomingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersIncomingWebhooks) return } if channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel) { c.LogAudit("fail - bad channel permissions") c.SetPermissionError(model.PermissionReadChannelContent) return } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), channel.TeamId, model.PermissionBypassIncomingWebhookChannelLock) { updatedHook.ChannelLocked = true updatedHook.ChannelId = channel.Id } incomingHook, err := c.App.UpdateIncomingWebhook(oldHook, &updatedHook) if err != nil { c.Err = err return } auditRec.AddEventResultState(incomingHook) auditRec.Success() c.LogAudit("success") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(incomingHook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) { var ( teamID = r.URL.Query().Get("team_id") userID = c.AppContext.Session().UserId hooks []*model.IncomingWebhook appErr *model.AppError js []byte err error ) if teamID != "" { if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamID, model.PermissionManageOwnIncomingWebhooks) { c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } // Remove userId as a filter if they have permission to manage others. if c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamID, model.PermissionManageOthersIncomingWebhooks) { userID = "" } hooks, appErr = c.App.GetIncomingWebhooksForTeamPageByUser(teamID, userID, c.Params.Page, c.Params.PerPage) } else { if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageOwnIncomingWebhooks) { c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } // Remove userId as a filter if they have permission to manage others. if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageOthersIncomingWebhooks) { userID = "" } hooks, appErr = c.App.GetIncomingWebhooksPageByUser(userID, c.Params.Page, c.Params.PerPage) } if appErr != nil { c.Err = appErr return } if c.Params.IncludeTotalCount { totalCount, appErr := c.App.GetIncomingWebhooksCount(teamID, userID) if appErr != nil { c.Err = appErr return } hooksWithCount := model.IncomingWebhooksWithCount{Webhooks: hooks, TotalCount: totalCount} js, err = json.Marshal(hooksWithCount) } else { js, err = json.Marshal(hooks) } if err != nil { c.Err = model.NewAppError("getIncomingHooks", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) return } if _, err := w.Write(js); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func getIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } hookId := c.Params.HookId var err *model.AppError var hook *model.IncomingWebhook var channel *model.Channel hook, err = c.App.GetIncomingWebhook(hookId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventGetIncomingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterToAuditRec(auditRec, "hook_id", c.Params.HookId) auditRec.AddMeta("hook_id", hook.Id) auditRec.AddMeta("hook_display", hook.DisplayName) auditRec.AddMeta("channel_id", hook.ChannelId) auditRec.AddMeta("team_id", hook.TeamId) c.LogAudit("attempt") channel, err = c.App.GetChannel(c.AppContext, hook.ChannelId) if err != nil { c.Err = err return } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || (channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } if c.AppContext.Session().UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersIncomingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersIncomingWebhooks) return } auditRec.Success() c.LogAudit("success") if err := json.NewEncoder(w).Encode(hook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } hookId := c.Params.HookId var err *model.AppError var hook *model.IncomingWebhook var channel *model.Channel hook, err = c.App.GetIncomingWebhook(hookId) if err != nil { c.Err = err return } channel, err = c.App.GetChannel(c.AppContext, hook.ChannelId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventDeleteIncomingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterToAuditRec(auditRec, "hook_id", c.Params.HookId) auditRec.AddMeta("hook_id", hook.Id) auditRec.AddMeta("hook_display", hook.DisplayName) auditRec.AddMeta("channel_id", channel.Id) auditRec.AddMeta("channel_name", channel.Name) auditRec.AddMeta("team_id", hook.TeamId) if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnIncomingWebhooks) || (channel.Type != model.ChannelTypeOpen && !c.App.SessionHasPermissionToReadChannel(c.AppContext, *c.AppContext.Session(), channel)) { c.LogAudit("fail - bad permissions") c.SetPermissionError(model.PermissionManageOwnIncomingWebhooks) return } if c.AppContext.Session().UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersIncomingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersIncomingWebhooks) return } if err = c.App.DeleteIncomingWebhook(hookId); err != nil { c.Err = err return } auditRec.AddEventPriorState(hook) auditRec.AddEventObjectType("incoming_webhook") auditRec.Success() ReturnStatusOK(w) } func updateOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } var updatedHook model.OutgoingWebhook if jsonErr := json.NewDecoder(r.Body).Decode(&updatedHook); jsonErr != nil { c.SetInvalidParamWithErr("outgoing_webhook", jsonErr) return } // The hook being updated in the payload must be the same one as indicated in the URL. if updatedHook.Id != c.Params.HookId { c.SetInvalidParam("hook_id") return } auditRec := c.MakeAuditRecord(model.AuditEventUpdateOutgoingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterAuditableToAuditRec(auditRec, "updated_hook", &updatedHook) c.LogAudit("attempt") oldHook, err := c.App.GetOutgoingWebhook(c.Params.HookId) if err != nil { c.Err = err return } if updatedHook.TeamId == "" { updatedHook.TeamId = oldHook.TeamId } if updatedHook.TeamId != oldHook.TeamId { c.Err = model.NewAppError("updateOutgoingHook", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.AppContext.Session().UserId, http.StatusBadRequest) return } if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), updatedHook.TeamId, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } if c.AppContext.Session().UserId != oldHook.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), updatedHook.TeamId, model.PermissionManageOthersOutgoingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersOutgoingWebhooks) return } updatedHook.CreatorId = c.AppContext.Session().UserId rhook, err := c.App.UpdateOutgoingWebhook(c.AppContext, oldHook, &updatedHook) if err != nil { c.Err = err return } auditRec.Success() c.LogAudit("success") if err := json.NewEncoder(w).Encode(rhook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { var hook model.OutgoingWebhook if jsonErr := json.NewDecoder(r.Body).Decode(&hook); jsonErr != nil { c.SetInvalidParamWithErr("outgoing_webhook", jsonErr) return } auditRec := c.MakeAuditRecord(model.AuditEventCreateOutgoingHook, model.AuditStatusFail) model.AddEventParameterAuditableToAuditRec(auditRec, "hook", &hook) defer c.LogAuditRec(auditRec) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } if hook.CreatorId == "" { hook.CreatorId = c.AppContext.Session().UserId } else { if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersOutgoingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersOutgoingWebhooks) return } _, err := c.App.GetUser(hook.CreatorId) if err != nil { c.Err = err return } } rhook, err := c.App.CreateOutgoingWebhook(&hook) if err != nil { c.LogAudit("fail") c.Err = err return } auditRec.Success() auditRec.AddEventResultState(rhook) auditRec.AddEventObjectType("outgoing_webhook") c.LogAudit("success") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(rhook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) { var ( query = r.URL.Query() channelID = query.Get("channel_id") teamID = query.Get("team_id") userID = c.AppContext.Session().UserId hooks []*model.OutgoingWebhook appErr *model.AppError ) if channelID != "" { if !c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } // Remove userId as a filter if they have permission to manage others. if c.App.SessionHasPermissionToChannel(c.AppContext, *c.AppContext.Session(), channelID, model.PermissionManageOthersOutgoingWebhooks) { userID = "" } hooks, appErr = c.App.GetOutgoingWebhooksForChannelPageByUser(channelID, userID, c.Params.Page, c.Params.PerPage) } else if teamID != "" { if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamID, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } // Remove userId as a filter if they have permission to manage others. if c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamID, model.PermissionManageOthersOutgoingWebhooks) { userID = "" } hooks, appErr = c.App.GetOutgoingWebhooksForTeamPageByUser(teamID, userID, c.Params.Page, c.Params.PerPage) } else { if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } // Remove userId as a filter if they have permission to manage others. if c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageOthersOutgoingWebhooks) { userID = "" } hooks, appErr = c.App.GetOutgoingWebhooksPageByUser(userID, c.Params.Page, c.Params.PerPage) } if appErr != nil { c.Err = appErr return } js, err := json.Marshal(hooks) if err != nil { c.Err = model.NewAppError("getOutgoingHooks", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(err) return } if _, err := w.Write(js); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func getOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } hook, err := c.App.GetOutgoingWebhook(c.Params.HookId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventGetOutgoingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterToAuditRec(auditRec, "hook_id", c.Params.HookId) auditRec.AddMeta("hook_id", hook.Id) auditRec.AddMeta("hook_display", hook.DisplayName) auditRec.AddMeta("channel_id", hook.ChannelId) auditRec.AddMeta("team_id", hook.TeamId) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } if c.AppContext.Session().UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersOutgoingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersOutgoingWebhooks) return } auditRec.Success() c.LogAudit("success") if err := json.NewEncoder(w).Encode(hook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } hook, err := c.App.GetOutgoingWebhook(c.Params.HookId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventRegenOutgoingHookToken, model.AuditStatusFail) defer c.LogAuditRec(auditRec) auditRec.AddMeta("hook_id", hook.Id) auditRec.AddMeta("hook_display", hook.DisplayName) auditRec.AddMeta("channel_id", hook.ChannelId) auditRec.AddMeta("team_id", hook.TeamId) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } if c.AppContext.Session().UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersOutgoingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersOutgoingWebhooks) return } rhook, err := c.App.RegenOutgoingWebhookToken(hook) if err != nil { c.Err = err return } auditRec.AddEventResultState(rhook) auditRec.AddEventObjectType("outgoing_webhook") auditRec.Success() c.LogAudit("success") if err := json.NewEncoder(w).Encode(rhook); err != nil { c.Logger.Warn("Error while writing response", mlog.Err(err)) } } func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) { c.RequireHookId() if c.Err != nil { return } hook, err := c.App.GetOutgoingWebhook(c.Params.HookId) if err != nil { c.Err = err return } auditRec := c.MakeAuditRecord(model.AuditEventDeleteOutgoingHook, model.AuditStatusFail) defer c.LogAuditRec(auditRec) model.AddEventParameterToAuditRec(auditRec, "hook_id", c.Params.HookId) auditRec.AddMeta("hook_id", hook.Id) auditRec.AddMeta("hook_display", hook.DisplayName) auditRec.AddMeta("channel_id", hook.ChannelId) auditRec.AddMeta("team_id", hook.TeamId) c.LogAudit("attempt") if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOwnOutgoingWebhooks) { c.SetPermissionError(model.PermissionManageOwnOutgoingWebhooks) return } if c.AppContext.Session().UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), hook.TeamId, model.PermissionManageOthersOutgoingWebhooks) { c.LogAudit("fail - inappropriate permissions") c.SetPermissionError(model.PermissionManageOthersOutgoingWebhooks) return } if err := c.App.DeleteOutgoingWebhook(hook.Id); err != nil { c.LogAudit("fail") c.Err = err return } auditRec.Success() c.LogAudit("success") ReturnStatusOK(w) }