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