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