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