github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/app/webhook.go (about) 1 // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "fmt" 8 "io" 9 "net/http" 10 "regexp" 11 "strings" 12 "unicode/utf8" 13 14 "github.com/mattermost/mattermost-server/mlog" 15 "github.com/mattermost/mattermost-server/model" 16 "github.com/mattermost/mattermost-server/store" 17 "github.com/mattermost/mattermost-server/utils" 18 ) 19 20 const ( 21 TRIGGERWORDS_EXACT_MATCH = 0 22 TRIGGERWORDS_STARTS_WITH = 1 23 24 MaxIntegrationResponseSize = 1024 * 1024 // Posts can be <100KB at most, so this is likely more than enough 25 ) 26 27 func (a *App) handleWebhookEvents(post *model.Post, team *model.Team, channel *model.Channel, user *model.User) *model.AppError { 28 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 29 return nil 30 } 31 32 if channel.Type != model.CHANNEL_OPEN { 33 return nil 34 } 35 36 hchan := a.Srv.Store.Webhook().GetOutgoingByTeam(team.Id, -1, -1) 37 result := <-hchan 38 if result.Err != nil { 39 return result.Err 40 } 41 42 hooks := result.Data.([]*model.OutgoingWebhook) 43 if len(hooks) == 0 { 44 return nil 45 } 46 47 var firstWord, triggerWord string 48 49 splitWords := strings.Fields(post.Message) 50 if len(splitWords) > 0 { 51 firstWord = splitWords[0] 52 } 53 54 relevantHooks := []*model.OutgoingWebhook{} 55 for _, hook := range hooks { 56 if hook.ChannelId == post.ChannelId || len(hook.ChannelId) == 0 { 57 if hook.ChannelId == post.ChannelId && len(hook.TriggerWords) == 0 { 58 relevantHooks = append(relevantHooks, hook) 59 triggerWord = "" 60 } else if hook.TriggerWhen == TRIGGERWORDS_EXACT_MATCH && hook.TriggerWordExactMatch(firstWord) { 61 relevantHooks = append(relevantHooks, hook) 62 triggerWord = hook.GetTriggerWord(firstWord, true) 63 } else if hook.TriggerWhen == TRIGGERWORDS_STARTS_WITH && hook.TriggerWordStartsWith(firstWord) { 64 relevantHooks = append(relevantHooks, hook) 65 triggerWord = hook.GetTriggerWord(firstWord, false) 66 } 67 } 68 } 69 70 for _, hook := range relevantHooks { 71 payload := &model.OutgoingWebhookPayload{ 72 Token: hook.Token, 73 TeamId: hook.TeamId, 74 TeamDomain: team.Name, 75 ChannelId: post.ChannelId, 76 ChannelName: channel.Name, 77 Timestamp: post.CreateAt, 78 UserId: post.UserId, 79 UserName: user.Username, 80 PostId: post.Id, 81 Text: post.Message, 82 TriggerWord: triggerWord, 83 FileIds: strings.Join(post.FileIds, ","), 84 } 85 a.Srv.Go(func(hook *model.OutgoingWebhook) func() { 86 return func() { 87 a.TriggerWebhook(payload, hook, post, channel) 88 } 89 }(hook)) 90 } 91 92 return nil 93 } 94 95 func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.OutgoingWebhook, post *model.Post, channel *model.Channel) { 96 var body io.Reader 97 var contentType string 98 if hook.ContentType == "application/json" { 99 body = strings.NewReader(payload.ToJSON()) 100 contentType = "application/json" 101 } else { 102 body = strings.NewReader(payload.ToFormValues()) 103 contentType = "application/x-www-form-urlencoded" 104 } 105 106 for i := range hook.CallbackURLs { 107 // Get the callback URL by index to properly capture it for the go func 108 url := hook.CallbackURLs[i] 109 110 a.Srv.Go(func() { 111 webhookResp, err := a.doOutgoingWebhookRequest(url, body, contentType) 112 if err != nil { 113 mlog.Error(fmt.Sprintf("Event POST failed, err=%s", err.Error())) 114 return 115 } 116 117 if webhookResp != nil && (webhookResp.Text != nil || len(webhookResp.Attachments) > 0) { 118 postRootId := "" 119 if webhookResp.ResponseType == model.OUTGOING_HOOK_RESPONSE_TYPE_COMMENT { 120 postRootId = post.Id 121 } 122 if len(webhookResp.Props) == 0 { 123 webhookResp.Props = make(model.StringInterface) 124 } 125 webhookResp.Props["webhook_display_name"] = hook.DisplayName 126 127 text := "" 128 if webhookResp.Text != nil { 129 text = a.ProcessSlackText(*webhookResp.Text) 130 } 131 webhookResp.Attachments = a.ProcessSlackAttachments(webhookResp.Attachments) 132 // attachments is in here for slack compatibility 133 if len(webhookResp.Attachments) > 0 { 134 webhookResp.Props["attachments"] = webhookResp.Attachments 135 } 136 if *a.Config().ServiceSettings.EnablePostUsernameOverride && hook.Username != "" && webhookResp.Username == "" { 137 webhookResp.Username = hook.Username 138 } 139 140 if *a.Config().ServiceSettings.EnablePostIconOverride && hook.IconURL != "" && webhookResp.IconURL == "" { 141 webhookResp.IconURL = hook.IconURL 142 } 143 if _, err := a.CreateWebhookPost(hook.CreatorId, channel, text, webhookResp.Username, webhookResp.IconURL, webhookResp.Props, webhookResp.Type, postRootId); err != nil { 144 mlog.Error(fmt.Sprintf("Failed to create response post, err=%v", err)) 145 } 146 } 147 }) 148 } 149 } 150 151 func (a *App) doOutgoingWebhookRequest(url string, body io.Reader, contentType string) (*model.OutgoingWebhookResponse, error) { 152 req, err := http.NewRequest("POST", url, body) 153 if err != nil { 154 return nil, err 155 } 156 157 req.Header.Set("Content-Type", contentType) 158 req.Header.Set("Accept", "application/json") 159 160 resp, err := a.HTTPService.MakeClient(false).Do(req) 161 if err != nil { 162 return nil, err 163 } 164 165 defer resp.Body.Close() 166 167 return model.OutgoingWebhookResponseFromJson(io.LimitReader(resp.Body, MaxIntegrationResponseSize)) 168 } 169 170 func SplitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.AppError) { 171 splits := make([]*model.Post, 0) 172 remainingText := post.Message 173 174 base := *post 175 base.Message = "" 176 base.Props = make(map[string]interface{}) 177 for k, v := range post.Props { 178 if k != "attachments" { 179 base.Props[k] = v 180 } 181 } 182 if utf8.RuneCountInString(model.StringInterfaceToJson(base.Props)) > model.POST_PROPS_MAX_USER_RUNES { 183 return nil, model.NewAppError("SplitWebhookPost", "web.incoming_webhook.split_props_length.app_error", map[string]interface{}{"Max": model.POST_PROPS_MAX_USER_RUNES}, "", http.StatusBadRequest) 184 } 185 186 for utf8.RuneCountInString(remainingText) > maxPostSize { 187 split := base 188 x := 0 189 for index := range remainingText { 190 x++ 191 if x > maxPostSize { 192 split.Message = remainingText[:index] 193 remainingText = remainingText[index:] 194 break 195 } 196 } 197 splits = append(splits, &split) 198 } 199 200 split := base 201 split.Message = remainingText 202 splits = append(splits, &split) 203 204 attachments, _ := post.Props["attachments"].([]*model.SlackAttachment) 205 for _, attachment := range attachments { 206 newAttachment := *attachment 207 for { 208 lastSplit := splits[len(splits)-1] 209 newProps := make(map[string]interface{}) 210 for k, v := range lastSplit.Props { 211 newProps[k] = v 212 } 213 origAttachments, _ := newProps["attachments"].([]*model.SlackAttachment) 214 newProps["attachments"] = append(origAttachments, &newAttachment) 215 newPropsString := model.StringInterfaceToJson(newProps) 216 runeCount := utf8.RuneCountInString(newPropsString) 217 218 if runeCount <= model.POST_PROPS_MAX_USER_RUNES { 219 lastSplit.Props = newProps 220 break 221 } 222 223 if len(origAttachments) > 0 { 224 newSplit := base 225 splits = append(splits, &newSplit) 226 continue 227 } 228 229 truncationNeeded := runeCount - model.POST_PROPS_MAX_USER_RUNES 230 textRuneCount := utf8.RuneCountInString(attachment.Text) 231 if textRuneCount < truncationNeeded { 232 return nil, model.NewAppError("SplitWebhookPost", "web.incoming_webhook.split_props_length.app_error", map[string]interface{}{"Max": model.POST_PROPS_MAX_USER_RUNES}, "", http.StatusBadRequest) 233 } 234 x := 0 235 for index := range attachment.Text { 236 x++ 237 if x > textRuneCount-truncationNeeded { 238 newAttachment.Text = newAttachment.Text[:index] 239 break 240 } 241 } 242 lastSplit.Props = newProps 243 break 244 } 245 } 246 247 return splits, nil 248 } 249 250 func (a *App) CreateWebhookPost(userId string, channel *model.Channel, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string, postRootId string) (*model.Post, *model.AppError) { 251 // parse links into Markdown format 252 linkWithTextRegex := regexp.MustCompile(`<([^\n<\|>]+)\|([^\n>]+)>`) 253 text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") 254 255 post := &model.Post{UserId: userId, ChannelId: channel.Id, Message: text, Type: postType, RootId: postRootId} 256 post.AddProp("from_webhook", "true") 257 258 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 259 err := model.NewAppError("CreateWebhookPost", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 260 return nil, err 261 } 262 263 if metrics := a.Metrics; metrics != nil { 264 metrics.IncrementWebhookPost() 265 } 266 267 if *a.Config().ServiceSettings.EnablePostUsernameOverride { 268 if len(overrideUsername) != 0 { 269 post.AddProp("override_username", overrideUsername) 270 } else { 271 post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME) 272 } 273 } 274 275 if *a.Config().ServiceSettings.EnablePostIconOverride { 276 if len(overrideIconUrl) != 0 { 277 post.AddProp("override_icon_url", overrideIconUrl) 278 } 279 } 280 281 if len(props) > 0 { 282 for key, val := range props { 283 if key == "attachments" { 284 if attachments, success := val.([]*model.SlackAttachment); success { 285 model.ParseSlackAttachment(post, attachments) 286 } 287 } else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { 288 post.AddProp(key, val) 289 } 290 } 291 } 292 293 splits, err := SplitWebhookPost(post, a.MaxPostSize()) 294 if err != nil { 295 return nil, err 296 } 297 298 for _, split := range splits { 299 if _, err := a.CreatePostMissingChannel(split, false); err != nil { 300 return nil, model.NewAppError("CreateWebhookPost", "api.post.create_webhook_post.creating.app_error", nil, "err="+err.Message, http.StatusInternalServerError) 301 } 302 } 303 304 return splits[0], nil 305 } 306 307 func (a *App) CreateIncomingWebhookForChannel(creatorId string, channel *model.Channel, hook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError) { 308 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 309 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 310 } 311 312 hook.UserId = creatorId 313 hook.TeamId = channel.TeamId 314 315 if !*a.Config().ServiceSettings.EnablePostUsernameOverride { 316 hook.Username = "" 317 } 318 if !*a.Config().ServiceSettings.EnablePostIconOverride { 319 hook.IconURL = "" 320 } 321 322 if hook.Username != "" && !model.IsValidUsername(hook.Username) { 323 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "api.incoming_webhook.invalid_username.app_error", nil, "", http.StatusBadRequest) 324 } 325 326 if result := <-a.Srv.Store.Webhook().SaveIncoming(hook); result.Err != nil { 327 return nil, result.Err 328 } else { 329 return result.Data.(*model.IncomingWebhook), nil 330 } 331 } 332 333 func (a *App) UpdateIncomingWebhook(oldHook, updatedHook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError) { 334 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 335 return nil, model.NewAppError("UpdateIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 336 } 337 338 if !*a.Config().ServiceSettings.EnablePostUsernameOverride { 339 updatedHook.Username = oldHook.Username 340 } 341 if !*a.Config().ServiceSettings.EnablePostIconOverride { 342 updatedHook.IconURL = oldHook.IconURL 343 } 344 345 if updatedHook.Username != "" && !model.IsValidUsername(updatedHook.Username) { 346 return nil, model.NewAppError("UpdateIncomingWebhook", "api.incoming_webhook.invalid_username.app_error", nil, "", http.StatusBadRequest) 347 } 348 349 updatedHook.Id = oldHook.Id 350 updatedHook.UserId = oldHook.UserId 351 updatedHook.CreateAt = oldHook.CreateAt 352 updatedHook.UpdateAt = model.GetMillis() 353 updatedHook.TeamId = oldHook.TeamId 354 updatedHook.DeleteAt = oldHook.DeleteAt 355 356 if result := <-a.Srv.Store.Webhook().UpdateIncoming(updatedHook); result.Err != nil { 357 return nil, result.Err 358 } else { 359 a.InvalidateCacheForWebhook(oldHook.Id) 360 return result.Data.(*model.IncomingWebhook), nil 361 } 362 } 363 364 func (a *App) DeleteIncomingWebhook(hookId string) *model.AppError { 365 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 366 return model.NewAppError("DeleteIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 367 } 368 369 if result := <-a.Srv.Store.Webhook().DeleteIncoming(hookId, model.GetMillis()); result.Err != nil { 370 return result.Err 371 } 372 373 a.InvalidateCacheForWebhook(hookId) 374 375 return nil 376 } 377 378 func (a *App) GetIncomingWebhook(hookId string) (*model.IncomingWebhook, *model.AppError) { 379 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 380 return nil, model.NewAppError("GetIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 381 } 382 383 if result := <-a.Srv.Store.Webhook().GetIncoming(hookId, true); result.Err != nil { 384 return nil, result.Err 385 } else { 386 return result.Data.(*model.IncomingWebhook), nil 387 } 388 } 389 390 func (a *App) GetIncomingWebhooksForTeamPage(teamId string, page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 391 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 392 return nil, model.NewAppError("GetIncomingWebhooksForTeamPage", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 393 } 394 395 if result := <-a.Srv.Store.Webhook().GetIncomingByTeam(teamId, page*perPage, perPage); result.Err != nil { 396 return nil, result.Err 397 } else { 398 return result.Data.([]*model.IncomingWebhook), nil 399 } 400 } 401 402 func (a *App) GetIncomingWebhooksPage(page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 403 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 404 return nil, model.NewAppError("GetIncomingWebhooksPage", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 405 } 406 407 if result := <-a.Srv.Store.Webhook().GetIncomingList(page*perPage, perPage); result.Err != nil { 408 return nil, result.Err 409 } else { 410 return result.Data.([]*model.IncomingWebhook), nil 411 } 412 } 413 414 func (a *App) CreateOutgoingWebhook(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 415 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 416 return nil, model.NewAppError("CreateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 417 } 418 419 if len(hook.ChannelId) != 0 { 420 cchan := a.Srv.Store.Channel().Get(hook.ChannelId, true) 421 422 var channel *model.Channel 423 if result := <-cchan; result.Err != nil { 424 return nil, result.Err 425 } else { 426 channel = result.Data.(*model.Channel) 427 } 428 429 if channel.Type != model.CHANNEL_OPEN { 430 return nil, model.NewAppError("CreateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusForbidden) 431 } 432 433 if channel.Type != model.CHANNEL_OPEN || channel.TeamId != hook.TeamId { 434 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.permissions.app_error", nil, "", http.StatusForbidden) 435 } 436 } else if len(hook.TriggerWords) == 0 { 437 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.triggers.app_error", nil, "", http.StatusBadRequest) 438 } 439 440 if result := <-a.Srv.Store.Webhook().GetOutgoingByTeam(hook.TeamId, -1, -1); result.Err != nil { 441 return nil, result.Err 442 } else { 443 allHooks := result.Data.([]*model.OutgoingWebhook) 444 445 for _, existingOutHook := range allHooks { 446 urlIntersect := utils.StringArrayIntersection(existingOutHook.CallbackURLs, hook.CallbackURLs) 447 triggerIntersect := utils.StringArrayIntersection(existingOutHook.TriggerWords, hook.TriggerWords) 448 449 if existingOutHook.ChannelId == hook.ChannelId && len(urlIntersect) != 0 && len(triggerIntersect) != 0 { 450 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.intersect.app_error", nil, "", http.StatusInternalServerError) 451 } 452 } 453 } 454 455 if result := <-a.Srv.Store.Webhook().SaveOutgoing(hook); result.Err != nil { 456 return nil, result.Err 457 } else { 458 return result.Data.(*model.OutgoingWebhook), nil 459 } 460 } 461 462 func (a *App) UpdateOutgoingWebhook(oldHook, updatedHook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 463 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 464 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 465 } 466 467 if len(updatedHook.ChannelId) > 0 { 468 channel, err := a.GetChannel(updatedHook.ChannelId) 469 if err != nil { 470 return nil, err 471 } 472 473 if channel.Type != model.CHANNEL_OPEN { 474 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.not_open.app_error", nil, "", http.StatusForbidden) 475 } 476 477 if channel.TeamId != oldHook.TeamId { 478 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.permissions.app_error", nil, "", http.StatusForbidden) 479 } 480 } else if len(updatedHook.TriggerWords) == 0 { 481 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.triggers.app_error", nil, "", http.StatusInternalServerError) 482 } 483 484 var result store.StoreResult 485 if result = <-a.Srv.Store.Webhook().GetOutgoingByTeam(oldHook.TeamId, -1, -1); result.Err != nil { 486 return nil, result.Err 487 } 488 489 allHooks := result.Data.([]*model.OutgoingWebhook) 490 491 for _, existingOutHook := range allHooks { 492 urlIntersect := utils.StringArrayIntersection(existingOutHook.CallbackURLs, updatedHook.CallbackURLs) 493 triggerIntersect := utils.StringArrayIntersection(existingOutHook.TriggerWords, updatedHook.TriggerWords) 494 495 if existingOutHook.ChannelId == updatedHook.ChannelId && len(urlIntersect) != 0 && len(triggerIntersect) != 0 && existingOutHook.Id != updatedHook.Id { 496 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.update_outgoing.intersect.app_error", nil, "", http.StatusBadRequest) 497 } 498 } 499 500 updatedHook.CreatorId = oldHook.CreatorId 501 updatedHook.CreateAt = oldHook.CreateAt 502 updatedHook.DeleteAt = oldHook.DeleteAt 503 updatedHook.TeamId = oldHook.TeamId 504 updatedHook.UpdateAt = model.GetMillis() 505 506 if result = <-a.Srv.Store.Webhook().UpdateOutgoing(updatedHook); result.Err != nil { 507 return nil, result.Err 508 } else { 509 return result.Data.(*model.OutgoingWebhook), nil 510 } 511 } 512 513 func (a *App) GetOutgoingWebhook(hookId string) (*model.OutgoingWebhook, *model.AppError) { 514 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 515 return nil, model.NewAppError("GetOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 516 } 517 518 if result := <-a.Srv.Store.Webhook().GetOutgoing(hookId); result.Err != nil { 519 return nil, result.Err 520 } else { 521 return result.Data.(*model.OutgoingWebhook), nil 522 } 523 } 524 525 func (a *App) GetOutgoingWebhooksPage(page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 526 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 527 return nil, model.NewAppError("GetOutgoingWebhooksPage", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 528 } 529 530 if result := <-a.Srv.Store.Webhook().GetOutgoingList(page*perPage, perPage); result.Err != nil { 531 return nil, result.Err 532 } else { 533 return result.Data.([]*model.OutgoingWebhook), nil 534 } 535 } 536 537 func (a *App) GetOutgoingWebhooksForChannelPage(channelId string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 538 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 539 return nil, model.NewAppError("GetOutgoingWebhooksForChannelPage", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 540 } 541 542 if result := <-a.Srv.Store.Webhook().GetOutgoingByChannel(channelId, page*perPage, perPage); result.Err != nil { 543 return nil, result.Err 544 } else { 545 return result.Data.([]*model.OutgoingWebhook), nil 546 } 547 } 548 549 func (a *App) GetOutgoingWebhooksForTeamPage(teamId string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 550 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 551 return nil, model.NewAppError("GetOutgoingWebhooksForTeamPage", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 552 } 553 554 if result := <-a.Srv.Store.Webhook().GetOutgoingByTeam(teamId, page*perPage, perPage); result.Err != nil { 555 return nil, result.Err 556 } else { 557 return result.Data.([]*model.OutgoingWebhook), nil 558 } 559 } 560 561 func (a *App) DeleteOutgoingWebhook(hookId string) *model.AppError { 562 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 563 return model.NewAppError("DeleteOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 564 } 565 566 if result := <-a.Srv.Store.Webhook().DeleteOutgoing(hookId, model.GetMillis()); result.Err != nil { 567 return result.Err 568 } 569 570 return nil 571 } 572 573 func (a *App) RegenOutgoingWebhookToken(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 574 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 575 return nil, model.NewAppError("RegenOutgoingWebhookToken", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 576 } 577 578 hook.Token = model.NewId() 579 580 if result := <-a.Srv.Store.Webhook().UpdateOutgoing(hook); result.Err != nil { 581 return nil, result.Err 582 } else { 583 return result.Data.(*model.OutgoingWebhook), nil 584 } 585 } 586 587 func (a *App) HandleIncomingWebhook(hookId string, req *model.IncomingWebhookRequest) *model.AppError { 588 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 589 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 590 } 591 592 hchan := a.Srv.Store.Webhook().GetIncoming(hookId, true) 593 594 if req == nil { 595 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.parse.app_error", nil, "", http.StatusBadRequest) 596 } 597 598 text := req.Text 599 if len(text) == 0 && req.Attachments == nil { 600 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.text.app_error", nil, "", http.StatusBadRequest) 601 } 602 603 channelName := req.ChannelName 604 webhookType := req.Type 605 606 var hook *model.IncomingWebhook 607 if result := <-hchan; result.Err != nil { 608 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.invalid.app_error", nil, "err="+result.Err.Message, http.StatusBadRequest) 609 } else { 610 hook = result.Data.(*model.IncomingWebhook) 611 } 612 613 uchan := a.Srv.Store.User().Get(hook.UserId) 614 615 if len(req.Props) == 0 { 616 req.Props = make(model.StringInterface) 617 } 618 619 req.Props["webhook_display_name"] = hook.DisplayName 620 621 text = a.ProcessSlackText(text) 622 req.Attachments = a.ProcessSlackAttachments(req.Attachments) 623 // attachments is in here for slack compatibility 624 if len(req.Attachments) > 0 { 625 req.Props["attachments"] = req.Attachments 626 webhookType = model.POST_SLACK_ATTACHMENT 627 } 628 629 var channel *model.Channel 630 var cchan store.StoreChannel 631 632 if len(channelName) != 0 { 633 if channelName[0] == '@' { 634 if result := <-a.Srv.Store.User().GetByUsername(channelName[1:]); result.Err != nil { 635 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message, http.StatusBadRequest) 636 } else { 637 if ch, err := a.GetOrCreateDirectChannel(hook.UserId, result.Data.(*model.User).Id); err != nil { 638 return err 639 } else { 640 channel = ch 641 } 642 } 643 } else if channelName[0] == '#' { 644 cchan = a.Srv.Store.Channel().GetByName(hook.TeamId, channelName[1:], true) 645 } else { 646 cchan = a.Srv.Store.Channel().GetByName(hook.TeamId, channelName, true) 647 } 648 } else { 649 cchan = a.Srv.Store.Channel().Get(hook.ChannelId, true) 650 } 651 652 if channel == nil { 653 result := <-cchan 654 if result.Err != nil { 655 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode) 656 } else { 657 channel = result.Data.(*model.Channel) 658 } 659 } 660 661 if hook.ChannelLocked && hook.ChannelId != channel.Id { 662 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel_locked.app_error", nil, "", http.StatusForbidden) 663 } 664 665 var user *model.User 666 if result := <-uchan; result.Err != nil { 667 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", nil, "err="+result.Err.Message, http.StatusForbidden) 668 } else { 669 user = result.Data.(*model.User) 670 } 671 672 if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 673 channel.Name == model.DEFAULT_CHANNEL && !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 674 return model.NewAppError("HandleIncomingWebhook", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 675 } 676 677 if channel.Type != model.CHANNEL_OPEN && !a.HasPermissionToChannel(hook.UserId, channel.Id, model.PERMISSION_READ_CHANNEL) { 678 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.permissions.app_error", nil, "", http.StatusForbidden) 679 } 680 681 overrideUsername := hook.Username 682 if req.Username != "" { 683 overrideUsername = req.Username 684 } 685 686 overrideIconUrl := hook.IconURL 687 if req.IconURL != "" { 688 overrideIconUrl = req.IconURL 689 } 690 691 _, err := a.CreateWebhookPost(hook.UserId, channel, text, overrideUsername, overrideIconUrl, req.Props, webhookType, "") 692 return err 693 } 694 695 func (a *App) CreateCommandWebhook(commandId string, args *model.CommandArgs) (*model.CommandWebhook, *model.AppError) { 696 hook := &model.CommandWebhook{ 697 CommandId: commandId, 698 UserId: args.UserId, 699 ChannelId: args.ChannelId, 700 RootId: args.RootId, 701 ParentId: args.ParentId, 702 } 703 704 if result := <-a.Srv.Store.CommandWebhook().Save(hook); result.Err != nil { 705 return nil, result.Err 706 } else { 707 return result.Data.(*model.CommandWebhook), nil 708 } 709 } 710 711 func (a *App) HandleCommandWebhook(hookId string, response *model.CommandResponse) *model.AppError { 712 if response == nil { 713 return model.NewAppError("HandleCommandWebhook", "web.command_webhook.parse.app_error", nil, "", http.StatusBadRequest) 714 } 715 716 var hook *model.CommandWebhook 717 if result := <-a.Srv.Store.CommandWebhook().Get(hookId); result.Err != nil { 718 return model.NewAppError("HandleCommandWebhook", "web.command_webhook.invalid.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode) 719 } else { 720 hook = result.Data.(*model.CommandWebhook) 721 } 722 723 var cmd *model.Command 724 if result := <-a.Srv.Store.Command().Get(hook.CommandId); result.Err != nil { 725 return model.NewAppError("HandleCommandWebhook", "web.command_webhook.command.app_error", nil, "err="+result.Err.Message, http.StatusBadRequest) 726 } else { 727 cmd = result.Data.(*model.Command) 728 } 729 730 args := &model.CommandArgs{ 731 UserId: hook.UserId, 732 ChannelId: hook.ChannelId, 733 TeamId: cmd.TeamId, 734 RootId: hook.RootId, 735 ParentId: hook.ParentId, 736 } 737 738 if result := <-a.Srv.Store.CommandWebhook().TryUse(hook.Id, 5); result.Err != nil { 739 return model.NewAppError("HandleCommandWebhook", "web.command_webhook.invalid.app_error", nil, "err="+result.Err.Message, result.Err.StatusCode) 740 } 741 742 _, err := a.HandleCommandResponse(cmd, args, response, false) 743 return err 744 }