github.com/spline-fu/mattermost-server@v4.10.10+incompatible/api4/webhook.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"strings"
    11  
    12  	"github.com/gorilla/mux"
    13  	"github.com/gorilla/schema"
    14  	"github.com/mattermost/mattermost-server/mlog"
    15  	"github.com/mattermost/mattermost-server/model"
    16  )
    17  
    18  func (api *API) InitWebhook() {
    19  	api.BaseRoutes.IncomingHooks.Handle("", api.ApiSessionRequired(createIncomingHook)).Methods("POST")
    20  	api.BaseRoutes.IncomingHooks.Handle("", api.ApiSessionRequired(getIncomingHooks)).Methods("GET")
    21  	api.BaseRoutes.IncomingHook.Handle("", api.ApiSessionRequired(getIncomingHook)).Methods("GET")
    22  	api.BaseRoutes.IncomingHook.Handle("", api.ApiSessionRequired(updateIncomingHook)).Methods("PUT")
    23  	api.BaseRoutes.IncomingHook.Handle("", api.ApiSessionRequired(deleteIncomingHook)).Methods("DELETE")
    24  
    25  	api.BaseRoutes.OutgoingHooks.Handle("", api.ApiSessionRequired(createOutgoingHook)).Methods("POST")
    26  	api.BaseRoutes.OutgoingHooks.Handle("", api.ApiSessionRequired(getOutgoingHooks)).Methods("GET")
    27  	api.BaseRoutes.OutgoingHook.Handle("", api.ApiSessionRequired(getOutgoingHook)).Methods("GET")
    28  	api.BaseRoutes.OutgoingHook.Handle("", api.ApiSessionRequired(updateOutgoingHook)).Methods("PUT")
    29  	api.BaseRoutes.OutgoingHook.Handle("", api.ApiSessionRequired(deleteOutgoingHook)).Methods("DELETE")
    30  	api.BaseRoutes.OutgoingHook.Handle("/regen_token", api.ApiSessionRequired(regenOutgoingHookToken)).Methods("POST")
    31  
    32  	api.BaseRoutes.Root.Handle("/hooks/commands/{id:[A-Za-z0-9]+}", api.ApiHandler(commandWebhook)).Methods("POST")
    33  	api.BaseRoutes.Root.Handle("/hooks/{id:[A-Za-z0-9]+}", api.ApiHandler(incomingWebhook)).Methods("POST")
    34  
    35  	// Old endpoint for backwards compatibility
    36  	api.BaseRoutes.Root.Handle("/api/v3/teams/{team_id:[A-Za-z0-9]+}/hooks/{id:[A-Za-z0-9]+}", api.ApiHandler(incomingWebhook)).Methods("POST")
    37  }
    38  
    39  func createIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
    40  	hook := model.IncomingWebhookFromJson(r.Body)
    41  	if hook == nil {
    42  		c.SetInvalidParam("incoming_webhook")
    43  		return
    44  	}
    45  
    46  	channel, err := c.App.GetChannel(hook.ChannelId)
    47  	if err != nil {
    48  		c.Err = err
    49  		return
    50  	}
    51  
    52  	c.LogAudit("attempt")
    53  
    54  	if !c.App.SessionHasPermissionToTeam(c.Session, channel.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
    55  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
    56  		return
    57  	}
    58  
    59  	if channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) {
    60  		c.LogAudit("fail - bad channel permissions")
    61  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
    62  		return
    63  	}
    64  
    65  	if incomingHook, err := c.App.CreateIncomingWebhookForChannel(c.Session.UserId, channel, hook); err != nil {
    66  		c.Err = err
    67  		return
    68  	} else {
    69  		c.LogAudit("success")
    70  		w.WriteHeader(http.StatusCreated)
    71  		w.Write([]byte(incomingHook.ToJson()))
    72  	}
    73  }
    74  
    75  func updateIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
    76  	c.RequireHookId()
    77  	if c.Err != nil {
    78  		return
    79  	}
    80  
    81  	hookId := c.Params.HookId
    82  
    83  	updatedHook := model.IncomingWebhookFromJson(r.Body)
    84  	if updatedHook == nil {
    85  		c.SetInvalidParam("incoming_webhook")
    86  		return
    87  	}
    88  
    89  	c.LogAudit("attempt")
    90  
    91  	oldHook, err := c.App.GetIncomingWebhook(hookId)
    92  	if err != nil {
    93  		c.Err = err
    94  		return
    95  	}
    96  
    97  	if updatedHook.TeamId == "" {
    98  		updatedHook.TeamId = oldHook.TeamId
    99  	}
   100  
   101  	if updatedHook.TeamId != oldHook.TeamId {
   102  		c.Err = model.NewAppError("updateIncomingHook", "api.webhook.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest)
   103  		return
   104  	}
   105  
   106  	if !c.App.SessionHasPermissionToTeam(c.Session, updatedHook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   107  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   108  		return
   109  	}
   110  
   111  	if c.Session.UserId != oldHook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, updatedHook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   112  		c.LogAudit("fail - inappropriate permissions")
   113  		c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   114  		return
   115  	}
   116  
   117  	channel, err := c.App.GetChannel(updatedHook.ChannelId)
   118  	if err != nil {
   119  		c.Err = err
   120  		return
   121  	}
   122  
   123  	if channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, channel.Id, model.PERMISSION_READ_CHANNEL) {
   124  		c.LogAudit("fail - bad channel permissions")
   125  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   126  		return
   127  	}
   128  
   129  	if incomingHook, err := c.App.UpdateIncomingWebhook(oldHook, updatedHook); err != nil {
   130  		c.Err = err
   131  		return
   132  	} else {
   133  		c.LogAudit("success")
   134  		w.WriteHeader(http.StatusCreated)
   135  		w.Write([]byte(incomingHook.ToJson()))
   136  	}
   137  }
   138  
   139  func getIncomingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
   140  	teamId := r.URL.Query().Get("team_id")
   141  
   142  	var hooks []*model.IncomingWebhook
   143  	var err *model.AppError
   144  
   145  	if len(teamId) > 0 {
   146  		if !c.App.SessionHasPermissionToTeam(c.Session, teamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   147  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   148  			return
   149  		}
   150  
   151  		hooks, err = c.App.GetIncomingWebhooksForTeamPage(teamId, c.Params.Page, c.Params.PerPage)
   152  	} else {
   153  		if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_WEBHOOKS) {
   154  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   155  			return
   156  		}
   157  
   158  		hooks, err = c.App.GetIncomingWebhooksPage(c.Params.Page, c.Params.PerPage)
   159  	}
   160  
   161  	if err != nil {
   162  		c.Err = err
   163  		return
   164  	}
   165  
   166  	w.Write([]byte(model.IncomingWebhookListToJson(hooks)))
   167  }
   168  
   169  func getIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   170  	c.RequireHookId()
   171  	if c.Err != nil {
   172  		return
   173  	}
   174  
   175  	hookId := c.Params.HookId
   176  
   177  	var err *model.AppError
   178  	var hook *model.IncomingWebhook
   179  	var channel *model.Channel
   180  
   181  	if hook, err = c.App.GetIncomingWebhook(hookId); err != nil {
   182  		c.Err = err
   183  		return
   184  	} else {
   185  		channel, err = c.App.GetChannel(hook.ChannelId)
   186  		if err != nil {
   187  			c.Err = err
   188  			return
   189  		}
   190  
   191  		if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) ||
   192  			(channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, hook.ChannelId, model.PERMISSION_READ_CHANNEL)) {
   193  			c.LogAudit("fail - bad permissions")
   194  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   195  			return
   196  		}
   197  
   198  		if c.Session.UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   199  			c.LogAudit("fail - inappropriate permissions")
   200  			c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   201  			return
   202  		}
   203  
   204  		w.Write([]byte(hook.ToJson()))
   205  		return
   206  	}
   207  }
   208  
   209  func deleteIncomingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   210  	c.RequireHookId()
   211  	if c.Err != nil {
   212  		return
   213  	}
   214  
   215  	hookId := c.Params.HookId
   216  
   217  	var err *model.AppError
   218  	var hook *model.IncomingWebhook
   219  	var channel *model.Channel
   220  
   221  	if hook, err = c.App.GetIncomingWebhook(hookId); err != nil {
   222  		c.Err = err
   223  		return
   224  	} else {
   225  		channel, err = c.App.GetChannel(hook.ChannelId)
   226  		if err != nil {
   227  			c.Err = err
   228  			return
   229  		}
   230  
   231  		if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) ||
   232  			(channel.Type != model.CHANNEL_OPEN && !c.App.SessionHasPermissionToChannel(c.Session, hook.ChannelId, model.PERMISSION_READ_CHANNEL)) {
   233  			c.LogAudit("fail - bad permissions")
   234  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   235  			return
   236  		}
   237  
   238  		if c.Session.UserId != hook.UserId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   239  			c.LogAudit("fail - inappropriate permissions")
   240  			c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   241  			return
   242  		}
   243  
   244  		if err = c.App.DeleteIncomingWebhook(hookId); err != nil {
   245  			c.Err = err
   246  			return
   247  		}
   248  
   249  		ReturnStatusOK(w)
   250  	}
   251  }
   252  
   253  func updateOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   254  	c.RequireHookId()
   255  	if c.Err != nil {
   256  		return
   257  	}
   258  
   259  	toUpdateHook := model.OutgoingWebhookFromJson(r.Body)
   260  	if toUpdateHook == nil {
   261  		c.SetInvalidParam("outgoing_webhook")
   262  		return
   263  	}
   264  
   265  	c.LogAudit("attempt")
   266  
   267  	toUpdateHook.CreatorId = c.Session.UserId
   268  
   269  	if !c.App.SessionHasPermissionToTeam(c.Session, toUpdateHook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   270  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   271  		return
   272  	}
   273  
   274  	oldHook, err := c.App.GetOutgoingWebhook(toUpdateHook.Id)
   275  	if err != nil {
   276  		c.Err = err
   277  		return
   278  	}
   279  
   280  	if c.Session.UserId != oldHook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, oldHook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   281  		c.LogAudit("fail - inappropriate permissions")
   282  		c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   283  		return
   284  	}
   285  
   286  	rhook, err := c.App.UpdateOutgoingWebhook(oldHook, toUpdateHook)
   287  	if err != nil {
   288  		c.Err = err
   289  		return
   290  	}
   291  
   292  	c.LogAudit("success")
   293  	w.Write([]byte(rhook.ToJson()))
   294  }
   295  
   296  func createOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   297  	hook := model.OutgoingWebhookFromJson(r.Body)
   298  	if hook == nil {
   299  		c.SetInvalidParam("outgoing_webhook")
   300  		return
   301  	}
   302  
   303  	c.LogAudit("attempt")
   304  
   305  	hook.CreatorId = c.Session.UserId
   306  
   307  	if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   308  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   309  		return
   310  	}
   311  
   312  	if rhook, err := c.App.CreateOutgoingWebhook(hook); err != nil {
   313  		c.LogAudit("fail")
   314  		c.Err = err
   315  		return
   316  	} else {
   317  		c.LogAudit("success")
   318  		w.WriteHeader(http.StatusCreated)
   319  		w.Write([]byte(rhook.ToJson()))
   320  	}
   321  }
   322  
   323  func getOutgoingHooks(c *Context, w http.ResponseWriter, r *http.Request) {
   324  	channelId := r.URL.Query().Get("channel_id")
   325  	teamId := r.URL.Query().Get("team_id")
   326  
   327  	var hooks []*model.OutgoingWebhook
   328  	var err *model.AppError
   329  
   330  	if len(channelId) > 0 {
   331  		if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_MANAGE_WEBHOOKS) {
   332  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   333  			return
   334  		}
   335  
   336  		hooks, err = c.App.GetOutgoingWebhooksForChannelPage(channelId, c.Params.Page, c.Params.PerPage)
   337  	} else if len(teamId) > 0 {
   338  		if !c.App.SessionHasPermissionToTeam(c.Session, teamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   339  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   340  			return
   341  		}
   342  
   343  		hooks, err = c.App.GetOutgoingWebhooksForTeamPage(teamId, c.Params.Page, c.Params.PerPage)
   344  	} else {
   345  		if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_WEBHOOKS) {
   346  			c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   347  			return
   348  		}
   349  
   350  		hooks, err = c.App.GetOutgoingWebhooksPage(c.Params.Page, c.Params.PerPage)
   351  	}
   352  
   353  	if err != nil {
   354  		c.Err = err
   355  		return
   356  	}
   357  
   358  	w.Write([]byte(model.OutgoingWebhookListToJson(hooks)))
   359  }
   360  
   361  func getOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   362  	c.RequireHookId()
   363  	if c.Err != nil {
   364  		return
   365  	}
   366  
   367  	hook, err := c.App.GetOutgoingWebhook(c.Params.HookId)
   368  	if err != nil {
   369  		c.Err = err
   370  		return
   371  	}
   372  
   373  	c.LogAudit("attempt")
   374  
   375  	if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   376  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   377  		return
   378  	}
   379  
   380  	if c.Session.UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   381  		c.LogAudit("fail - inappropriate permissions")
   382  		c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   383  		return
   384  	}
   385  
   386  	c.LogAudit("success")
   387  	w.Write([]byte(hook.ToJson()))
   388  }
   389  
   390  func regenOutgoingHookToken(c *Context, w http.ResponseWriter, r *http.Request) {
   391  	c.RequireHookId()
   392  	if c.Err != nil {
   393  		return
   394  	}
   395  
   396  	hook, err := c.App.GetOutgoingWebhook(c.Params.HookId)
   397  	if err != nil {
   398  		c.Err = err
   399  		return
   400  	}
   401  
   402  	c.LogAudit("attempt")
   403  
   404  	if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   405  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   406  		return
   407  	}
   408  
   409  	if c.Session.UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   410  		c.LogAudit("fail - inappropriate permissions")
   411  		c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   412  		return
   413  	}
   414  
   415  	if rhook, err := c.App.RegenOutgoingWebhookToken(hook); err != nil {
   416  		c.Err = err
   417  		return
   418  	} else {
   419  		w.Write([]byte(rhook.ToJson()))
   420  	}
   421  }
   422  
   423  func deleteOutgoingHook(c *Context, w http.ResponseWriter, r *http.Request) {
   424  	c.RequireHookId()
   425  	if c.Err != nil {
   426  		return
   427  	}
   428  
   429  	hook, err := c.App.GetOutgoingWebhook(c.Params.HookId)
   430  	if err != nil {
   431  		c.Err = err
   432  		return
   433  	}
   434  
   435  	c.LogAudit("attempt")
   436  
   437  	if !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_WEBHOOKS) {
   438  		c.SetPermissionError(model.PERMISSION_MANAGE_WEBHOOKS)
   439  		return
   440  	}
   441  
   442  	if c.Session.UserId != hook.CreatorId && !c.App.SessionHasPermissionToTeam(c.Session, hook.TeamId, model.PERMISSION_MANAGE_OTHERS_WEBHOOKS) {
   443  		c.LogAudit("fail - inappropriate permissions")
   444  		c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_WEBHOOKS)
   445  		return
   446  	}
   447  
   448  	if err := c.App.DeleteOutgoingWebhook(hook.Id); err != nil {
   449  		c.LogAudit("fail")
   450  		c.Err = err
   451  		return
   452  	}
   453  
   454  	c.LogAudit("success")
   455  	ReturnStatusOK(w)
   456  }
   457  
   458  func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
   459  	params := mux.Vars(r)
   460  	id := params["id"]
   461  
   462  	r.ParseForm()
   463  
   464  	var err *model.AppError
   465  	incomingWebhookPayload := &model.IncomingWebhookRequest{}
   466  	contentType := r.Header.Get("Content-Type")
   467  	if strings.Split(contentType, "; ")[0] == "application/x-www-form-urlencoded" {
   468  		payload := strings.NewReader(r.FormValue("payload"))
   469  
   470  		incomingWebhookPayload, err = decodePayload(payload)
   471  		if err != nil {
   472  			c.Err = err
   473  			return
   474  		}
   475  	} else if strings.HasPrefix(contentType, "multipart/form-data") {
   476  		r.ParseMultipartForm(0)
   477  
   478  		decoder := schema.NewDecoder()
   479  		err := decoder.Decode(incomingWebhookPayload, r.PostForm)
   480  
   481  		if err != nil {
   482  			c.Err = model.NewAppError("incomingWebhook", "api.webhook.incoming.error", nil, err.Error(), http.StatusBadRequest)
   483  			return
   484  		}
   485  	} else {
   486  		incomingWebhookPayload, err = decodePayload(r.Body)
   487  		if err != nil {
   488  			c.Err = err
   489  			return
   490  		}
   491  	}
   492  
   493  	if c.App.Config().LogSettings.EnableWebhookDebugging {
   494  		mlog.Debug(fmt.Sprint("Incoming webhook received. Content=", incomingWebhookPayload.ToJson()))
   495  	}
   496  
   497  	err = c.App.HandleIncomingWebhook(id, incomingWebhookPayload)
   498  	if err != nil {
   499  		c.Err = err
   500  		return
   501  	}
   502  
   503  	w.Header().Set("Content-Type", "text/plain")
   504  	w.Write([]byte("ok"))
   505  }
   506  
   507  func commandWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
   508  	params := mux.Vars(r)
   509  	id := params["id"]
   510  
   511  	response, err := model.CommandResponseFromHTTPBody(r.Header.Get("Content-Type"), r.Body)
   512  	if err != nil {
   513  		c.Err = model.NewAppError("commandWebhook", "web.command_webhook.parse.app_error", nil, err.Error(), http.StatusBadRequest)
   514  		return
   515  	}
   516  
   517  	appErr := c.App.HandleCommandWebhook(id, response)
   518  	if appErr != nil {
   519  		c.Err = appErr
   520  		return
   521  	}
   522  
   523  	w.Header().Set("Content-Type", "text/plain")
   524  	w.Write([]byte("ok"))
   525  }
   526  
   527  func decodePayload(payload io.Reader) (*model.IncomingWebhookRequest, *model.AppError) {
   528  	incomingWebhookPayload, decodeError := model.IncomingWebhookRequestFromJson(payload)
   529  
   530  	if decodeError != nil {
   531  		return nil, decodeError
   532  	}
   533  
   534  	return incomingWebhookPayload, nil
   535  }