github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/webhook.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "context" 8 "errors" 9 "io" 10 "net/http" 11 "regexp" 12 "strings" 13 "unicode/utf8" 14 15 "github.com/mattermost/mattermost-server/v5/mlog" 16 "github.com/mattermost/mattermost-server/v5/model" 17 "github.com/mattermost/mattermost-server/v5/store" 18 "github.com/mattermost/mattermost-server/v5/utils" 19 ) 20 21 const ( 22 TriggerwordsExactMatch = 0 23 TriggerwordsStartsWith = 1 24 25 MaxIntegrationResponseSize = 1024 * 1024 // Posts can be <100KB at most, so this is likely more than enough 26 ) 27 28 func (a *App) handleWebhookEvents(post *model.Post, team *model.Team, channel *model.Channel, user *model.User) *model.AppError { 29 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 30 return nil 31 } 32 33 if channel.Type != model.CHANNEL_OPEN { 34 return nil 35 } 36 37 hooks, err := a.Srv().Store.Webhook().GetOutgoingByTeam(team.Id, -1, -1) 38 if err != nil { 39 return model.NewAppError("handleWebhookEvents", "app.webhooks.get_outgoing_by_team.app_error", nil, err.Error(), http.StatusInternalServerError) 40 } 41 42 if len(hooks) == 0 { 43 return nil 44 } 45 46 var firstWord, triggerWord string 47 48 splitWords := strings.Fields(post.Message) 49 if len(splitWords) > 0 { 50 firstWord = splitWords[0] 51 } 52 53 relevantHooks := []*model.OutgoingWebhook{} 54 for _, hook := range hooks { 55 if hook.ChannelId == post.ChannelId || hook.ChannelId == "" { 56 if hook.ChannelId == post.ChannelId && len(hook.TriggerWords) == 0 { 57 relevantHooks = append(relevantHooks, hook) 58 triggerWord = "" 59 } else if hook.TriggerWhen == TriggerwordsExactMatch && hook.TriggerWordExactMatch(firstWord) { 60 relevantHooks = append(relevantHooks, hook) 61 triggerWord = hook.GetTriggerWord(firstWord, true) 62 } else if hook.TriggerWhen == TriggerwordsStartsWith && hook.TriggerWordStartsWith(firstWord) { 63 relevantHooks = append(relevantHooks, hook) 64 triggerWord = hook.GetTriggerWord(firstWord, false) 65 } 66 } 67 } 68 69 for _, hook := range relevantHooks { 70 payload := &model.OutgoingWebhookPayload{ 71 Token: hook.Token, 72 TeamId: hook.TeamId, 73 TeamDomain: team.Name, 74 ChannelId: post.ChannelId, 75 ChannelName: channel.Name, 76 Timestamp: post.CreateAt, 77 UserId: post.UserId, 78 UserName: user.Username, 79 PostId: post.Id, 80 Text: post.Message, 81 TriggerWord: triggerWord, 82 FileIds: strings.Join(post.FileIds, ","), 83 } 84 a.Srv().Go(func(hook *model.OutgoingWebhook) func() { 85 return func() { 86 a.TriggerWebhook(payload, hook, post, channel) 87 } 88 }(hook)) 89 } 90 91 return nil 92 } 93 94 func (a *App) TriggerWebhook(payload *model.OutgoingWebhookPayload, hook *model.OutgoingWebhook, post *model.Post, channel *model.Channel) { 95 var body io.Reader 96 var contentType string 97 if hook.ContentType == "application/json" { 98 body = strings.NewReader(payload.ToJSON()) 99 contentType = "application/json" 100 } else { 101 body = strings.NewReader(payload.ToFormValues()) 102 contentType = "application/x-www-form-urlencoded" 103 } 104 105 for i := range hook.CallbackURLs { 106 // Get the callback URL by index to properly capture it for the go func 107 url := hook.CallbackURLs[i] 108 109 a.Srv().Go(func() { 110 webhookResp, err := a.doOutgoingWebhookRequest(url, body, contentType) 111 if err != nil { 112 mlog.Error("Event POST failed.", mlog.Err(err)) 113 return 114 } 115 116 if webhookResp != nil && (webhookResp.Text != nil || len(webhookResp.Attachments) > 0) { 117 postRootId := "" 118 if webhookResp.ResponseType == model.OUTGOING_HOOK_RESPONSE_TYPE_COMMENT { 119 postRootId = post.Id 120 } 121 if len(webhookResp.Props) == 0 { 122 webhookResp.Props = make(model.StringInterface) 123 } 124 webhookResp.Props["webhook_display_name"] = hook.DisplayName 125 126 text := "" 127 if webhookResp.Text != nil { 128 text = a.ProcessSlackText(*webhookResp.Text) 129 } 130 webhookResp.Attachments = a.ProcessSlackAttachments(webhookResp.Attachments) 131 // attachments is in here for slack compatibility 132 if len(webhookResp.Attachments) > 0 { 133 webhookResp.Props["attachments"] = webhookResp.Attachments 134 } 135 if *a.Config().ServiceSettings.EnablePostUsernameOverride && hook.Username != "" && webhookResp.Username == "" { 136 webhookResp.Username = hook.Username 137 } 138 139 if *a.Config().ServiceSettings.EnablePostIconOverride && hook.IconURL != "" && webhookResp.IconURL == "" { 140 webhookResp.IconURL = hook.IconURL 141 } 142 if _, err := a.CreateWebhookPost(hook.CreatorId, channel, text, webhookResp.Username, webhookResp.IconURL, "", webhookResp.Props, webhookResp.Type, postRootId); err != nil { 143 mlog.Error("Failed to create response post.", mlog.Err(err)) 144 } 145 } 146 }) 147 } 148 } 149 150 func (a *App) doOutgoingWebhookRequest(url string, body io.Reader, contentType string) (*model.OutgoingWebhookResponse, error) { 151 req, err := http.NewRequest("POST", url, body) 152 if err != nil { 153 return nil, err 154 } 155 156 req.Header.Set("Content-Type", contentType) 157 req.Header.Set("Accept", "application/json") 158 159 resp, err := a.HTTPService().MakeClient(false).Do(req) 160 if err != nil { 161 return nil, err 162 } 163 164 defer resp.Body.Close() 165 166 return model.OutgoingWebhookResponseFromJson(io.LimitReader(resp.Body, MaxIntegrationResponseSize)) 167 } 168 169 func SplitWebhookPost(post *model.Post, maxPostSize int) ([]*model.Post, *model.AppError) { 170 splits := make([]*model.Post, 0) 171 remainingText := post.Message 172 173 base := post.Clone() 174 base.Message = "" 175 base.SetProps(make(map[string]interface{})) 176 for k, v := range post.GetProps() { 177 if k != "attachments" { 178 base.AddProp(k, v) 179 } 180 } 181 182 if utf8.RuneCountInString(model.StringInterfaceToJson(base.GetProps())) > 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.Clone() 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.Clone() 201 split.Message = remainingText 202 splits = append(splits, split) 203 204 attachments, _ := post.GetProp("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.GetProps() { 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.SetProps(newProps) 220 break 221 } 222 223 if len(origAttachments) > 0 { 224 newSplit := base.Clone() 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.SetProps(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, overrideIconEmoji 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 overrideUsername != "" { 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 overrideIconURL != "" { 277 post.AddProp("override_icon_url", overrideIconURL) 278 } 279 if overrideIconEmoji != "" { 280 post.AddProp("override_icon_emoji", overrideIconEmoji) 281 } 282 } 283 284 if len(props) > 0 { 285 for key, val := range props { 286 if key == "attachments" { 287 if attachments, success := val.([]*model.SlackAttachment); success { 288 model.ParseSlackAttachment(post, attachments) 289 } 290 } else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" { 291 post.AddProp(key, val) 292 } 293 } 294 } 295 296 splits, err := SplitWebhookPost(post, a.MaxPostSize()) 297 if err != nil { 298 return nil, err 299 } 300 301 for _, split := range splits { 302 if _, err := a.CreatePostMissingChannel(split, false); err != nil { 303 return nil, model.NewAppError("CreateWebhookPost", "api.post.create_webhook_post.creating.app_error", nil, "err="+err.Message, http.StatusInternalServerError) 304 } 305 } 306 307 return splits[0], nil 308 } 309 310 func (a *App) CreateIncomingWebhookForChannel(creatorId string, channel *model.Channel, hook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError) { 311 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 312 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 313 } 314 315 hook.UserId = creatorId 316 hook.TeamId = channel.TeamId 317 318 if !*a.Config().ServiceSettings.EnablePostUsernameOverride { 319 hook.Username = "" 320 } 321 if !*a.Config().ServiceSettings.EnablePostIconOverride { 322 hook.IconURL = "" 323 } 324 325 if hook.Username != "" && !model.IsValidUsername(hook.Username) { 326 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "api.incoming_webhook.invalid_username.app_error", nil, "", http.StatusBadRequest) 327 } 328 329 webhook, err := a.Srv().Store.Webhook().SaveIncoming(hook) 330 if err != nil { 331 var invErr *store.ErrInvalidInput 332 var appErr *model.AppError 333 switch { 334 case errors.As(err, &appErr): 335 return nil, appErr 336 case errors.As(err, &invErr): 337 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "app.webhooks.save_incoming.existing.app_error", nil, invErr.Error(), http.StatusBadRequest) 338 default: 339 return nil, model.NewAppError("CreateIncomingWebhookForChannel", "app.webhooks.save_incoming.app_error", nil, err.Error(), http.StatusInternalServerError) 340 } 341 } 342 343 return webhook, nil 344 } 345 346 func (a *App) UpdateIncomingWebhook(oldHook, updatedHook *model.IncomingWebhook) (*model.IncomingWebhook, *model.AppError) { 347 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 348 return nil, model.NewAppError("UpdateIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 349 } 350 351 if !*a.Config().ServiceSettings.EnablePostUsernameOverride { 352 updatedHook.Username = oldHook.Username 353 } 354 if !*a.Config().ServiceSettings.EnablePostIconOverride { 355 updatedHook.IconURL = oldHook.IconURL 356 } 357 358 if updatedHook.Username != "" && !model.IsValidUsername(updatedHook.Username) { 359 return nil, model.NewAppError("UpdateIncomingWebhook", "api.incoming_webhook.invalid_username.app_error", nil, "", http.StatusBadRequest) 360 } 361 362 updatedHook.Id = oldHook.Id 363 updatedHook.UserId = oldHook.UserId 364 updatedHook.CreateAt = oldHook.CreateAt 365 updatedHook.UpdateAt = model.GetMillis() 366 updatedHook.TeamId = oldHook.TeamId 367 updatedHook.DeleteAt = oldHook.DeleteAt 368 369 newWebhook, err := a.Srv().Store.Webhook().UpdateIncoming(updatedHook) 370 if err != nil { 371 return nil, model.NewAppError("UpdateIncomingWebhook", "app.webhooks.update_incoming.app_error", nil, err.Error(), http.StatusInternalServerError) 372 } 373 a.invalidateCacheForWebhook(oldHook.Id) 374 return newWebhook, nil 375 } 376 377 func (a *App) DeleteIncomingWebhook(hookID string) *model.AppError { 378 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 379 return model.NewAppError("DeleteIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 380 } 381 382 if err := a.Srv().Store.Webhook().DeleteIncoming(hookID, model.GetMillis()); err != nil { 383 return model.NewAppError("DeleteIncomingWebhook", "app.webhooks.delete_incoming.app_error", nil, err.Error(), http.StatusInternalServerError) 384 } 385 386 a.invalidateCacheForWebhook(hookID) 387 388 return nil 389 } 390 391 func (a *App) GetIncomingWebhook(hookID string) (*model.IncomingWebhook, *model.AppError) { 392 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 393 return nil, model.NewAppError("GetIncomingWebhook", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 394 } 395 396 webhook, err := a.Srv().Store.Webhook().GetIncoming(hookID, true) 397 if err != nil { 398 var nfErr *store.ErrNotFound 399 switch { 400 case errors.As(err, &nfErr): 401 return nil, model.NewAppError("GetIncomingWebhook", "app.webhooks.get_incoming.app_error", nil, nfErr.Error(), http.StatusNotFound) 402 default: 403 return nil, model.NewAppError("GetIncomingWebhook", "app.webhooks.get_incoming.app_error", nil, err.Error(), http.StatusInternalServerError) 404 } 405 } 406 407 return webhook, nil 408 } 409 410 func (a *App) GetIncomingWebhooksForTeamPage(teamID string, page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 411 return a.GetIncomingWebhooksForTeamPageByUser(teamID, "", page, perPage) 412 } 413 414 func (a *App) GetIncomingWebhooksForTeamPageByUser(teamID string, userID string, page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 415 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 416 return nil, model.NewAppError("GetIncomingWebhooksForTeamPage", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 417 } 418 419 webhooks, err := a.Srv().Store.Webhook().GetIncomingByTeamByUser(teamID, userID, page*perPage, perPage) 420 if err != nil { 421 return nil, model.NewAppError("GetIncomingWebhooksForTeamPage", "app.webhooks.get_incoming_by_user.app_error", nil, err.Error(), http.StatusInternalServerError) 422 } 423 424 return webhooks, nil 425 } 426 427 func (a *App) GetIncomingWebhooksPageByUser(userID string, page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 428 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 429 return nil, model.NewAppError("GetIncomingWebhooksPageByUser", "api.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 430 } 431 432 webhooks, err := a.Srv().Store.Webhook().GetIncomingListByUser(userID, page*perPage, perPage) 433 if err != nil { 434 return nil, model.NewAppError("GetIncomingWebhooksPageByUser", "app.webhooks.get_incoming_by_user.app_error", nil, err.Error(), http.StatusInternalServerError) 435 } 436 437 return webhooks, nil 438 } 439 440 func (a *App) GetIncomingWebhooksPage(page, perPage int) ([]*model.IncomingWebhook, *model.AppError) { 441 return a.GetIncomingWebhooksPageByUser("", page, perPage) 442 } 443 444 func (a *App) CreateOutgoingWebhook(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 445 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 446 return nil, model.NewAppError("CreateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 447 } 448 449 if hook.ChannelId != "" { 450 channel, errCh := a.Srv().Store.Channel().Get(hook.ChannelId, true) 451 if errCh != nil { 452 var nfErr *store.ErrNotFound 453 switch { 454 case errors.As(errCh, &nfErr): 455 return nil, model.NewAppError("CreateOutgoingWebhook", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound) 456 default: 457 return nil, model.NewAppError("CreateOutgoingWebhook", "app.channel.get.find.app_error", nil, errCh.Error(), http.StatusInternalServerError) 458 } 459 } 460 461 if channel.Type != model.CHANNEL_OPEN { 462 return nil, model.NewAppError("CreateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusForbidden) 463 } 464 465 if channel.Type != model.CHANNEL_OPEN || channel.TeamId != hook.TeamId { 466 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.permissions.app_error", nil, "", http.StatusForbidden) 467 } 468 } else if len(hook.TriggerWords) == 0 { 469 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.triggers.app_error", nil, "", http.StatusBadRequest) 470 } 471 472 allHooks, err := a.Srv().Store.Webhook().GetOutgoingByTeam(hook.TeamId, -1, -1) 473 if err != nil { 474 return nil, model.NewAppError("CreateOutgoingWebhook", "app.webhooks.get_outgoing_by_team.app_error", nil, err.Error(), http.StatusInternalServerError) 475 } 476 for _, existingOutHook := range allHooks { 477 urlIntersect := utils.StringArrayIntersection(existingOutHook.CallbackURLs, hook.CallbackURLs) 478 triggerIntersect := utils.StringArrayIntersection(existingOutHook.TriggerWords, hook.TriggerWords) 479 480 if existingOutHook.ChannelId == hook.ChannelId && len(urlIntersect) != 0 && len(triggerIntersect) != 0 { 481 return nil, model.NewAppError("CreateOutgoingWebhook", "api.webhook.create_outgoing.intersect.app_error", nil, "", http.StatusInternalServerError) 482 } 483 } 484 485 webhook, err := a.Srv().Store.Webhook().SaveOutgoing(hook) 486 if err != nil { 487 var appErr *model.AppError 488 var invErr *store.ErrInvalidInput 489 switch { 490 case errors.As(err, &appErr): 491 return nil, appErr 492 case errors.As(err, &invErr): 493 return nil, model.NewAppError("CreateOutgoingWebhook", "app.webhooks.save_outgoing.override.app_error", nil, invErr.Error(), http.StatusBadRequest) 494 default: 495 return nil, model.NewAppError("CreateOutgoingWebhook", "app.webhooks.save_outgoing.app_error", nil, err.Error(), http.StatusInternalServerError) 496 } 497 } 498 499 return webhook, nil 500 } 501 502 func (a *App) UpdateOutgoingWebhook(oldHook, updatedHook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 503 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 504 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 505 } 506 507 if updatedHook.ChannelId != "" { 508 channel, err := a.GetChannel(updatedHook.ChannelId) 509 if err != nil { 510 return nil, err 511 } 512 513 if channel.Type != model.CHANNEL_OPEN { 514 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.not_open.app_error", nil, "", http.StatusForbidden) 515 } 516 517 if channel.TeamId != oldHook.TeamId { 518 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.permissions.app_error", nil, "", http.StatusForbidden) 519 } 520 } else if len(updatedHook.TriggerWords) == 0 { 521 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.create_outgoing.triggers.app_error", nil, "", http.StatusInternalServerError) 522 } 523 524 allHooks, err := a.Srv().Store.Webhook().GetOutgoingByTeam(oldHook.TeamId, -1, -1) 525 if err != nil { 526 return nil, model.NewAppError("UpdateOutgoingWebhook", "app.webhooks.get_outgoing_by_team.app_error", nil, err.Error(), http.StatusInternalServerError) 527 } 528 529 for _, existingOutHook := range allHooks { 530 urlIntersect := utils.StringArrayIntersection(existingOutHook.CallbackURLs, updatedHook.CallbackURLs) 531 triggerIntersect := utils.StringArrayIntersection(existingOutHook.TriggerWords, updatedHook.TriggerWords) 532 533 if existingOutHook.ChannelId == updatedHook.ChannelId && len(urlIntersect) != 0 && len(triggerIntersect) != 0 && existingOutHook.Id != updatedHook.Id { 534 return nil, model.NewAppError("UpdateOutgoingWebhook", "api.webhook.update_outgoing.intersect.app_error", nil, "", http.StatusBadRequest) 535 } 536 } 537 538 updatedHook.CreatorId = oldHook.CreatorId 539 updatedHook.CreateAt = oldHook.CreateAt 540 updatedHook.DeleteAt = oldHook.DeleteAt 541 updatedHook.TeamId = oldHook.TeamId 542 updatedHook.UpdateAt = model.GetMillis() 543 544 webhook, err := a.Srv().Store.Webhook().UpdateOutgoing(updatedHook) 545 if err != nil { 546 return nil, model.NewAppError("UpdateOutgoingWebhook", "app.webhooks.update_outgoing.app_error", nil, err.Error(), http.StatusInternalServerError) 547 } 548 549 return webhook, nil 550 } 551 552 func (a *App) GetOutgoingWebhook(hookID string) (*model.OutgoingWebhook, *model.AppError) { 553 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 554 return nil, model.NewAppError("GetOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 555 } 556 557 webhook, err := a.Srv().Store.Webhook().GetOutgoing(hookID) 558 if err != nil { 559 var nfErr *store.ErrNotFound 560 switch { 561 case errors.As(err, &nfErr): 562 return nil, model.NewAppError("GetOutgoingWebhook", "app.webhooks.get_outgoing.app_error", nil, nfErr.Error(), http.StatusNotFound) 563 default: 564 return nil, model.NewAppError("GetOutgoingWebhook", "app.webhooks.get_outgoing.app_error", nil, err.Error(), http.StatusInternalServerError) 565 } 566 } 567 568 return webhook, nil 569 } 570 571 func (a *App) GetOutgoingWebhooksPage(page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 572 return a.GetOutgoingWebhooksPageByUser("", page, perPage) 573 } 574 575 func (a *App) GetOutgoingWebhooksPageByUser(userID string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 576 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 577 return nil, model.NewAppError("GetOutgoingWebhooksPageByUser", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 578 } 579 580 webhooks, err := a.Srv().Store.Webhook().GetOutgoingListByUser(userID, page*perPage, perPage) 581 if err != nil { 582 return nil, model.NewAppError("GetOutgoingWebhooksPageByUser", "app.webhooks.get_outgoing_by_channel.app_error", nil, err.Error(), http.StatusInternalServerError) 583 } 584 585 return webhooks, nil 586 } 587 588 func (a *App) GetOutgoingWebhooksForChannelPageByUser(channelId string, userID string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 589 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 590 return nil, model.NewAppError("GetOutgoingWebhooksForChannelPage", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 591 } 592 593 webhooks, err := a.Srv().Store.Webhook().GetOutgoingByChannelByUser(channelId, userID, page*perPage, perPage) 594 if err != nil { 595 return nil, model.NewAppError("GetOutgoingWebhooksForChannelPage", "app.webhooks.get_outgoing_by_channel.app_error", nil, err.Error(), http.StatusInternalServerError) 596 } 597 598 return webhooks, nil 599 } 600 601 func (a *App) GetOutgoingWebhooksForTeamPage(teamID string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 602 return a.GetOutgoingWebhooksForTeamPageByUser(teamID, "", page, perPage) 603 } 604 605 func (a *App) GetOutgoingWebhooksForTeamPageByUser(teamID string, userID string, page, perPage int) ([]*model.OutgoingWebhook, *model.AppError) { 606 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 607 return nil, model.NewAppError("GetOutgoingWebhooksForTeamPageByUser", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 608 } 609 610 webhooks, err := a.Srv().Store.Webhook().GetOutgoingByTeamByUser(teamID, userID, page*perPage, perPage) 611 if err != nil { 612 return nil, model.NewAppError("GetOutgoingWebhooksForTeamPageByUser", "app.webhooks.get_outgoing_by_team.app_error", nil, err.Error(), http.StatusInternalServerError) 613 } 614 615 return webhooks, nil 616 } 617 618 func (a *App) DeleteOutgoingWebhook(hookID string) *model.AppError { 619 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 620 return model.NewAppError("DeleteOutgoingWebhook", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 621 } 622 623 if err := a.Srv().Store.Webhook().DeleteOutgoing(hookID, model.GetMillis()); err != nil { 624 return model.NewAppError("DeleteOutgoingWebhook", "app.webhooks.delete_outgoing.app_error", nil, err.Error(), http.StatusInternalServerError) 625 } 626 627 return nil 628 } 629 630 func (a *App) RegenOutgoingWebhookToken(hook *model.OutgoingWebhook) (*model.OutgoingWebhook, *model.AppError) { 631 if !*a.Config().ServiceSettings.EnableOutgoingWebhooks { 632 return nil, model.NewAppError("RegenOutgoingWebhookToken", "api.outgoing_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 633 } 634 635 hook.Token = model.NewId() 636 637 webhook, err := a.Srv().Store.Webhook().UpdateOutgoing(hook) 638 if err != nil { 639 return nil, model.NewAppError("RegenOutgoingWebhookToken", "app.webhooks.update_outgoing.app_error", nil, err.Error(), http.StatusInternalServerError) 640 } 641 642 return webhook, nil 643 } 644 645 func (a *App) HandleIncomingWebhook(hookID string, req *model.IncomingWebhookRequest) *model.AppError { 646 if !*a.Config().ServiceSettings.EnableIncomingWebhooks { 647 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.disabled.app_error", nil, "", http.StatusNotImplemented) 648 } 649 650 hchan := make(chan store.StoreResult, 1) 651 go func() { 652 webhook, err := a.Srv().Store.Webhook().GetIncoming(hookID, true) 653 hchan <- store.StoreResult{Data: webhook, NErr: err} 654 close(hchan) 655 }() 656 657 if req == nil { 658 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.parse.app_error", nil, "", http.StatusBadRequest) 659 } 660 661 text := req.Text 662 if text == "" && req.Attachments == nil { 663 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.text.app_error", nil, "", http.StatusBadRequest) 664 } 665 666 channelName := req.ChannelName 667 webhookType := req.Type 668 669 var hook *model.IncomingWebhook 670 result := <-hchan 671 if result.NErr != nil { 672 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.invalid.app_error", nil, result.NErr.Error(), http.StatusBadRequest) 673 } 674 hook = result.Data.(*model.IncomingWebhook) 675 676 uchan := make(chan store.StoreResult, 1) 677 go func() { 678 user, err := a.Srv().Store.User().Get(context.Background(), hook.UserId) 679 uchan <- store.StoreResult{Data: user, NErr: err} 680 close(uchan) 681 }() 682 683 if len(req.Props) == 0 { 684 req.Props = make(model.StringInterface) 685 } 686 687 req.Props["webhook_display_name"] = hook.DisplayName 688 689 text = a.ProcessSlackText(text) 690 req.Attachments = a.ProcessSlackAttachments(req.Attachments) 691 // attachments is in here for slack compatibility 692 if len(req.Attachments) > 0 { 693 req.Props["attachments"] = req.Attachments 694 webhookType = model.POST_SLACK_ATTACHMENT 695 } 696 697 var channel *model.Channel 698 var cchan chan store.StoreResult 699 700 if channelName != "" { 701 if channelName[0] == '@' { 702 result, nErr := a.Srv().Store.User().GetByUsername(channelName[1:]) 703 if nErr != nil { 704 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", nil, nErr.Error(), http.StatusBadRequest) 705 } 706 ch, err := a.GetOrCreateDirectChannel(hook.UserId, result.Id) 707 if err != nil { 708 return err 709 } 710 channel = ch 711 } else if channelName[0] == '#' { 712 cchan = make(chan store.StoreResult, 1) 713 go func() { 714 chnn, chnnErr := a.Srv().Store.Channel().GetByName(hook.TeamId, channelName[1:], true) 715 cchan <- store.StoreResult{Data: chnn, NErr: chnnErr} 716 close(cchan) 717 }() 718 } else { 719 cchan = make(chan store.StoreResult, 1) 720 go func() { 721 chnn, chnnErr := a.Srv().Store.Channel().GetByName(hook.TeamId, channelName, true) 722 cchan <- store.StoreResult{Data: chnn, NErr: chnnErr} 723 close(cchan) 724 }() 725 } 726 } else { 727 var err error 728 channel, err = a.Srv().Store.Channel().Get(hook.ChannelId, true) 729 if err != nil { 730 var nfErr *store.ErrNotFound 731 switch { 732 case errors.As(err, &nfErr): 733 return model.NewAppError("HandleIncomingWebhook", "app.channel.get.existing.app_error", nil, nfErr.Error(), http.StatusNotFound) 734 default: 735 return model.NewAppError("HandleIncomingWebhook", "app.channel.get.find.app_error", nil, err.Error(), http.StatusInternalServerError) 736 } 737 } 738 } 739 740 if channel == nil { 741 result2 := <-cchan 742 if result2.NErr != nil { 743 var nfErr *store.ErrNotFound 744 switch { 745 case errors.As(result2.NErr, &nfErr): 746 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel.app_error", nil, nfErr.Error(), http.StatusNotFound) 747 default: 748 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel.app_error", nil, result2.NErr.Error(), http.StatusInternalServerError) 749 } 750 } 751 channel = result2.Data.(*model.Channel) 752 } 753 754 if hook.ChannelLocked && hook.ChannelId != channel.Id { 755 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.channel_locked.app_error", nil, "", http.StatusForbidden) 756 } 757 758 var user *model.User 759 result = <-uchan 760 if result.NErr != nil { 761 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.user.app_error", nil, result.NErr.Error(), http.StatusForbidden) 762 } 763 user = result.Data.(*model.User) 764 765 if a.Srv().License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 766 channel.Name == model.DEFAULT_CHANNEL && !a.RolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 767 return model.NewAppError("HandleIncomingWebhook", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 768 } 769 770 if channel.Type != model.CHANNEL_OPEN && !a.HasPermissionToChannel(hook.UserId, channel.Id, model.PERMISSION_READ_CHANNEL) { 771 return model.NewAppError("HandleIncomingWebhook", "web.incoming_webhook.permissions.app_error", nil, "", http.StatusForbidden) 772 } 773 774 overrideUsername := hook.Username 775 if req.Username != "" { 776 overrideUsername = req.Username 777 } 778 779 overrideIconURL := hook.IconURL 780 if req.IconURL != "" { 781 overrideIconURL = req.IconURL 782 } 783 784 _, err := a.CreateWebhookPost(hook.UserId, channel, text, overrideUsername, overrideIconURL, req.IconEmoji, req.Props, webhookType, "") 785 return err 786 } 787 788 func (a *App) CreateCommandWebhook(commandID string, args *model.CommandArgs) (*model.CommandWebhook, *model.AppError) { 789 hook := &model.CommandWebhook{ 790 CommandId: commandID, 791 UserId: args.UserId, 792 ChannelId: args.ChannelId, 793 RootId: args.RootId, 794 ParentId: args.ParentId, 795 } 796 797 savedHook, err := a.Srv().Store.CommandWebhook().Save(hook) 798 if err != nil { 799 var invErr *store.ErrInvalidInput 800 var appErr *model.AppError 801 switch { 802 case errors.As(err, &invErr): 803 return nil, model.NewAppError("CreateCommandWebhook", "app.command_webhook.create_command_webhook.existing", nil, invErr.Error(), http.StatusBadRequest) 804 case errors.As(err, &appErr): 805 return nil, appErr 806 default: 807 return nil, model.NewAppError("CreateCommandWebhook", "app.command_webhook.create_command_webhook.internal_error", nil, err.Error(), http.StatusInternalServerError) 808 } 809 810 } 811 return savedHook, nil 812 } 813 814 func (a *App) HandleCommandWebhook(hookID string, response *model.CommandResponse) *model.AppError { 815 if response == nil { 816 return model.NewAppError("HandleCommandWebhook", "app.command_webhook.handle_command_webhook.parse", nil, "", http.StatusBadRequest) 817 } 818 819 hook, nErr := a.Srv().Store.CommandWebhook().Get(hookID) 820 if nErr != nil { 821 var nfErr *store.ErrNotFound 822 switch { 823 case errors.As(nErr, &nfErr): 824 return model.NewAppError("HandleCommandWebhook", "app.command_webhook.get.missing", map[string]interface{}{"hook_id": hookID}, nfErr.Error(), http.StatusNotFound) 825 default: 826 return model.NewAppError("HandleCommandWebhook", "app.command_webhook.get.internal_error", nil, nErr.Error(), http.StatusInternalServerError) 827 } 828 } 829 830 cmd, cmdErr := a.Srv().Store.Command().Get(hook.CommandId) 831 if cmdErr != nil { 832 var appErr *model.AppError 833 switch { 834 case errors.As(cmdErr, &appErr): 835 return appErr 836 default: 837 return model.NewAppError("HandleCommandWebhook", "web.command_webhook.command.app_error", nil, "err="+cmdErr.Error(), http.StatusBadRequest) 838 } 839 } 840 841 args := &model.CommandArgs{ 842 UserId: hook.UserId, 843 ChannelId: hook.ChannelId, 844 TeamId: cmd.TeamId, 845 RootId: hook.RootId, 846 ParentId: hook.ParentId, 847 } 848 849 if nErr := a.Srv().Store.CommandWebhook().TryUse(hook.Id, 5); nErr != nil { 850 var invErr *store.ErrInvalidInput 851 switch { 852 case errors.As(nErr, &invErr): 853 return model.NewAppError("HandleCommandWebhook", "app.command_webhook.try_use.invalid", nil, invErr.Error(), http.StatusBadRequest) 854 default: 855 return model.NewAppError("HandleCommandWebhook", "app.command_webhook.try_use.internal_error", nil, nErr.Error(), http.StatusInternalServerError) 856 } 857 } 858 859 _, err := a.HandleCommandResponse(cmd, args, response, false) 860 return err 861 }