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