github.com/dschalla/mattermost-server@v4.8.1-rc1+incompatible/app/post.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 "crypto/hmac" 8 "crypto/sha1" 9 "encoding/hex" 10 "encoding/json" 11 "fmt" 12 "net/http" 13 "net/url" 14 "regexp" 15 "strings" 16 17 l4g "github.com/alecthomas/log4go" 18 "github.com/dyatlov/go-opengraph/opengraph" 19 "github.com/mattermost/mattermost-server/model" 20 "github.com/mattermost/mattermost-server/store" 21 "github.com/mattermost/mattermost-server/utils" 22 ) 23 24 var linkWithTextRegex = regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`) 25 26 func (a *App) CreatePostAsUser(post *model.Post) (*model.Post, *model.AppError) { 27 // Check that channel has not been deleted 28 var channel *model.Channel 29 if result := <-a.Srv.Store.Channel().Get(post.ChannelId, true); result.Err != nil { 30 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 31 return nil, err 32 } else { 33 channel = result.Data.(*model.Channel) 34 } 35 36 if strings.HasPrefix(post.Type, model.POST_SYSTEM_MESSAGE_PREFIX) { 37 err := model.NewAppError("CreatePostAsUser", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.type"}, "", http.StatusBadRequest) 38 return nil, err 39 } 40 41 if channel.DeleteAt != 0 { 42 err := model.NewAppError("createPost", "api.post.create_post.can_not_post_to_deleted.error", nil, "", http.StatusBadRequest) 43 return nil, err 44 } 45 46 if rp, err := a.CreatePost(post, channel, true); err != nil { 47 if err.Id == "api.post.create_post.root_id.app_error" || 48 err.Id == "api.post.create_post.channel_root_id.app_error" || 49 err.Id == "api.post.create_post.parent_id.app_error" { 50 err.StatusCode = http.StatusBadRequest 51 } 52 53 if err.Id == "api.post.create_post.town_square_read_only" { 54 uchan := a.Srv.Store.User().Get(post.UserId) 55 var user *model.User 56 if result := <-uchan; result.Err != nil { 57 return nil, result.Err 58 } else { 59 user = result.Data.(*model.User) 60 } 61 62 T := utils.GetUserTranslations(user.Locale) 63 a.SendEphemeralPost( 64 post.UserId, 65 &model.Post{ 66 ChannelId: channel.Id, 67 ParentId: post.ParentId, 68 RootId: post.RootId, 69 UserId: post.UserId, 70 Message: T("api.post.create_post.town_square_read_only"), 71 CreateAt: model.GetMillis() + 1, 72 }, 73 ) 74 } 75 76 return nil, err 77 } else { 78 // Update the LastViewAt only if the post does not have from_webhook prop set (eg. Zapier app) 79 if _, ok := post.Props["from_webhook"]; !ok { 80 if result := <-a.Srv.Store.Channel().UpdateLastViewedAt([]string{post.ChannelId}, post.UserId); result.Err != nil { 81 l4g.Error(utils.T("api.post.create_post.last_viewed.error"), post.ChannelId, post.UserId, result.Err) 82 } 83 84 if *a.Config().ServiceSettings.EnableChannelViewedMessages { 85 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CHANNEL_VIEWED, "", "", post.UserId, nil) 86 message.Add("channel_id", post.ChannelId) 87 a.Go(func() { 88 a.Publish(message) 89 }) 90 } 91 } 92 93 return rp, nil 94 } 95 96 } 97 98 func (a *App) CreatePostMissingChannel(post *model.Post, triggerWebhooks bool) (*model.Post, *model.AppError) { 99 var channel *model.Channel 100 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 101 if result := <-cchan; result.Err != nil { 102 return nil, result.Err 103 } else { 104 channel = result.Data.(*model.Channel) 105 } 106 107 return a.CreatePost(post, channel, triggerWebhooks) 108 } 109 110 func (a *App) CreatePost(post *model.Post, channel *model.Channel, triggerWebhooks bool) (*model.Post, *model.AppError) { 111 post.SanitizeProps() 112 113 var pchan store.StoreChannel 114 if len(post.RootId) > 0 { 115 pchan = a.Srv.Store.Post().Get(post.RootId) 116 } 117 118 uchan := a.Srv.Store.User().Get(post.UserId) 119 var user *model.User 120 if result := <-uchan; result.Err != nil { 121 return nil, result.Err 122 } else { 123 user = result.Data.(*model.User) 124 } 125 126 if a.License() != nil && *a.Config().TeamSettings.ExperimentalTownSquareIsReadOnly && 127 !post.IsSystemMessage() && 128 channel.Name == model.DEFAULT_CHANNEL && 129 !a.CheckIfRolesGrantPermission(user.GetRoles(), model.PERMISSION_MANAGE_SYSTEM.Id) { 130 return nil, model.NewAppError("createPost", "api.post.create_post.town_square_read_only", nil, "", http.StatusForbidden) 131 } 132 133 // Verify the parent/child relationships are correct 134 var parentPostList *model.PostList 135 if pchan != nil { 136 if presult := <-pchan; presult.Err != nil { 137 return nil, model.NewAppError("createPost", "api.post.create_post.root_id.app_error", nil, "", http.StatusBadRequest) 138 } else { 139 parentPostList = presult.Data.(*model.PostList) 140 if len(parentPostList.Posts) == 0 || !parentPostList.IsChannelId(post.ChannelId) { 141 return nil, model.NewAppError("createPost", "api.post.create_post.channel_root_id.app_error", nil, "", http.StatusInternalServerError) 142 } 143 144 if post.ParentId == "" { 145 post.ParentId = post.RootId 146 } 147 148 if post.RootId != post.ParentId { 149 parent := parentPostList.Posts[post.ParentId] 150 if parent == nil { 151 return nil, model.NewAppError("createPost", "api.post.create_post.parent_id.app_error", nil, "", http.StatusInternalServerError) 152 } 153 } 154 } 155 } 156 157 post.Hashtags, _ = model.ParseHashtags(post.Message) 158 159 if err := a.FillInPostProps(post, channel); err != nil { 160 return nil, err 161 } 162 163 var rpost *model.Post 164 if result := <-a.Srv.Store.Post().Save(post); result.Err != nil { 165 return nil, result.Err 166 } else { 167 rpost = result.Data.(*model.Post) 168 } 169 170 esInterface := a.Elasticsearch 171 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 172 a.Go(func() { 173 esInterface.IndexPost(rpost, channel.TeamId) 174 }) 175 } 176 177 if a.Metrics != nil { 178 a.Metrics.IncrementPostCreate() 179 } 180 181 if len(post.FileIds) > 0 { 182 // There's a rare bug where the client sends up duplicate FileIds so protect against that 183 post.FileIds = utils.RemoveDuplicatesFromStringArray(post.FileIds) 184 185 for _, fileId := range post.FileIds { 186 if result := <-a.Srv.Store.FileInfo().AttachToPost(fileId, post.Id); result.Err != nil { 187 l4g.Error(utils.T("api.post.create_post.attach_files.error"), post.Id, post.FileIds, post.UserId, result.Err) 188 } 189 } 190 191 if a.Metrics != nil { 192 a.Metrics.IncrementPostFileAttachment(len(post.FileIds)) 193 } 194 } 195 196 if err := a.handlePostEvents(rpost, user, channel, triggerWebhooks, parentPostList); err != nil { 197 return nil, err 198 } 199 200 return rpost, nil 201 } 202 203 // FillInPostProps should be invoked before saving posts to fill in properties such as 204 // channel_mentions. 205 // 206 // If channel is nil, FillInPostProps will look up the channel corresponding to the post. 207 func (a *App) FillInPostProps(post *model.Post, channel *model.Channel) *model.AppError { 208 channelMentions := post.ChannelMentions() 209 channelMentionsProp := make(map[string]interface{}) 210 211 if len(channelMentions) > 0 { 212 if channel == nil { 213 result := <-a.Srv.Store.Channel().GetForPost(post.Id) 214 if result.Err != nil { 215 return model.NewAppError("FillInPostProps", "api.context.invalid_param.app_error", map[string]interface{}{"Name": "post.channel_id"}, result.Err.Error(), http.StatusBadRequest) 216 } 217 channel = result.Data.(*model.Channel) 218 } 219 220 mentionedChannels, err := a.GetChannelsByNames(channelMentions, channel.TeamId) 221 if err != nil { 222 return err 223 } 224 225 for _, mentioned := range mentionedChannels { 226 if mentioned.Type == model.CHANNEL_OPEN { 227 channelMentionsProp[mentioned.Name] = map[string]interface{}{ 228 "display_name": mentioned.DisplayName, 229 } 230 } 231 } 232 } 233 234 if len(channelMentionsProp) > 0 { 235 post.AddProp("channel_mentions", channelMentionsProp) 236 } else if post.Props != nil { 237 delete(post.Props, "channel_mentions") 238 } 239 240 return nil 241 } 242 243 func (a *App) handlePostEvents(post *model.Post, user *model.User, channel *model.Channel, triggerWebhooks bool, parentPostList *model.PostList) *model.AppError { 244 var tchan store.StoreChannel 245 if len(channel.TeamId) > 0 { 246 tchan = a.Srv.Store.Team().Get(channel.TeamId) 247 } 248 249 var team *model.Team 250 if tchan != nil { 251 if result := <-tchan; result.Err != nil { 252 return result.Err 253 } else { 254 team = result.Data.(*model.Team) 255 } 256 } else { 257 // Blank team for DMs 258 team = &model.Team{} 259 } 260 261 a.InvalidateCacheForChannel(channel) 262 a.InvalidateCacheForChannelPosts(channel.Id) 263 264 if _, err := a.SendNotifications(post, team, channel, user, parentPostList); err != nil { 265 return err 266 } 267 268 if triggerWebhooks { 269 a.Go(func() { 270 if err := a.handleWebhookEvents(post, team, channel, user); err != nil { 271 l4g.Error(err.Error()) 272 } 273 }) 274 } 275 276 return nil 277 } 278 279 // This method only parses and processes the attachments, 280 // all else should be set in the post which is passed 281 func parseSlackAttachment(post *model.Post, attachments []*model.SlackAttachment) { 282 post.Type = model.POST_SLACK_ATTACHMENT 283 284 for _, attachment := range attachments { 285 attachment.Text = parseSlackLinksToMarkdown(attachment.Text) 286 attachment.Pretext = parseSlackLinksToMarkdown(attachment.Pretext) 287 288 for _, field := range attachment.Fields { 289 if value, ok := field.Value.(string); ok { 290 field.Value = parseSlackLinksToMarkdown(value) 291 } 292 } 293 } 294 post.AddProp("attachments", attachments) 295 } 296 297 func parseSlackLinksToMarkdown(text string) string { 298 return linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})") 299 } 300 301 func (a *App) SendEphemeralPost(userId string, post *model.Post) *model.Post { 302 post.Type = model.POST_EPHEMERAL 303 304 // fill in fields which haven't been specified which have sensible defaults 305 if post.Id == "" { 306 post.Id = model.NewId() 307 } 308 if post.CreateAt == 0 { 309 post.CreateAt = model.GetMillis() 310 } 311 if post.Props == nil { 312 post.Props = model.StringInterface{} 313 } 314 315 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, "", post.ChannelId, userId, nil) 316 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 317 318 a.Go(func() { 319 a.Publish(message) 320 }) 321 322 return post 323 } 324 325 func (a *App) UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) { 326 post.SanitizeProps() 327 328 var oldPost *model.Post 329 if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { 330 return nil, result.Err 331 } else { 332 oldPost = result.Data.(*model.PostList).Posts[post.Id] 333 334 if a.License() != nil { 335 if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_NEVER && post.Message != oldPost.Message { 336 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_denied.app_error", nil, "", http.StatusForbidden) 337 return nil, err 338 } 339 } 340 341 if oldPost == nil { 342 err := model.NewAppError("UpdatePost", "api.post.update_post.find.app_error", nil, "id="+post.Id, http.StatusBadRequest) 343 return nil, err 344 } 345 346 if oldPost.DeleteAt != 0 { 347 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_details.app_error", map[string]interface{}{"PostId": post.Id}, "", http.StatusBadRequest) 348 return nil, err 349 } 350 351 if oldPost.IsSystemMessage() { 352 err := model.NewAppError("UpdatePost", "api.post.update_post.system_message.app_error", nil, "id="+post.Id, http.StatusBadRequest) 353 return nil, err 354 } 355 356 if a.License() != nil { 357 if *a.Config().ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_TIME_LIMIT && model.GetMillis() > oldPost.CreateAt+int64(*a.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != oldPost.Message { 358 err := model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]interface{}{"timeLimit": *a.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest) 359 return nil, err 360 } 361 } 362 } 363 364 newPost := &model.Post{} 365 *newPost = *oldPost 366 367 if newPost.Message != post.Message { 368 newPost.Message = post.Message 369 newPost.EditAt = model.GetMillis() 370 newPost.Hashtags, _ = model.ParseHashtags(post.Message) 371 } 372 373 if !safeUpdate { 374 newPost.IsPinned = post.IsPinned 375 newPost.HasReactions = post.HasReactions 376 newPost.FileIds = post.FileIds 377 newPost.Props = post.Props 378 } 379 380 if err := a.FillInPostProps(post, nil); err != nil { 381 return nil, err 382 } 383 384 if result := <-a.Srv.Store.Post().Update(newPost, oldPost); result.Err != nil { 385 return nil, result.Err 386 } else { 387 rpost := result.Data.(*model.Post) 388 389 esInterface := a.Elasticsearch 390 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 391 a.Go(func() { 392 if rchannel := <-a.Srv.Store.Channel().GetForPost(rpost.Id); rchannel.Err != nil { 393 l4g.Error("Couldn't get channel %v for post %v for Elasticsearch indexing.", rpost.ChannelId, rpost.Id) 394 } else { 395 esInterface.IndexPost(rpost, rchannel.Data.(*model.Channel).TeamId) 396 } 397 }) 398 } 399 400 a.sendUpdatedPostEvent(rpost) 401 402 a.InvalidateCacheForChannelPosts(rpost.ChannelId) 403 404 return rpost, nil 405 } 406 } 407 408 func (a *App) PatchPost(postId string, patch *model.PostPatch) (*model.Post, *model.AppError) { 409 post, err := a.GetSinglePost(postId) 410 if err != nil { 411 return nil, err 412 } 413 414 post.Patch(patch) 415 416 updatedPost, err := a.UpdatePost(post, false) 417 if err != nil { 418 return nil, err 419 } 420 421 return updatedPost, nil 422 } 423 424 func (a *App) sendUpdatedPostEvent(post *model.Post) { 425 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_EDITED, "", post.ChannelId, "", nil) 426 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 427 428 a.Go(func() { 429 a.Publish(message) 430 }) 431 } 432 433 func (a *App) GetPostsPage(channelId string, page int, perPage int) (*model.PostList, *model.AppError) { 434 if result := <-a.Srv.Store.Post().GetPosts(channelId, page*perPage, perPage, true); result.Err != nil { 435 return nil, result.Err 436 } else { 437 return result.Data.(*model.PostList), nil 438 } 439 } 440 441 func (a *App) GetPosts(channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 442 if result := <-a.Srv.Store.Post().GetPosts(channelId, offset, limit, true); result.Err != nil { 443 return nil, result.Err 444 } else { 445 return result.Data.(*model.PostList), nil 446 } 447 } 448 449 func (a *App) GetPostsEtag(channelId string) string { 450 return (<-a.Srv.Store.Post().GetEtag(channelId, true)).Data.(string) 451 } 452 453 func (a *App) GetPostsSince(channelId string, time int64) (*model.PostList, *model.AppError) { 454 if result := <-a.Srv.Store.Post().GetPostsSince(channelId, time, true); result.Err != nil { 455 return nil, result.Err 456 } else { 457 return result.Data.(*model.PostList), nil 458 } 459 } 460 461 func (a *App) GetSinglePost(postId string) (*model.Post, *model.AppError) { 462 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 463 return nil, result.Err 464 } else { 465 return result.Data.(*model.Post), nil 466 } 467 } 468 469 func (a *App) GetPostThread(postId string) (*model.PostList, *model.AppError) { 470 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 471 return nil, result.Err 472 } else { 473 return result.Data.(*model.PostList), nil 474 } 475 } 476 477 func (a *App) GetFlaggedPosts(userId string, offset int, limit int) (*model.PostList, *model.AppError) { 478 if result := <-a.Srv.Store.Post().GetFlaggedPosts(userId, offset, limit); result.Err != nil { 479 return nil, result.Err 480 } else { 481 return result.Data.(*model.PostList), nil 482 } 483 } 484 485 func (a *App) GetFlaggedPostsForTeam(userId, teamId string, offset int, limit int) (*model.PostList, *model.AppError) { 486 if result := <-a.Srv.Store.Post().GetFlaggedPostsForTeam(userId, teamId, offset, limit); result.Err != nil { 487 return nil, result.Err 488 } else { 489 return result.Data.(*model.PostList), nil 490 } 491 } 492 493 func (a *App) GetFlaggedPostsForChannel(userId, channelId string, offset int, limit int) (*model.PostList, *model.AppError) { 494 if result := <-a.Srv.Store.Post().GetFlaggedPostsForChannel(userId, channelId, offset, limit); result.Err != nil { 495 return nil, result.Err 496 } else { 497 return result.Data.(*model.PostList), nil 498 } 499 } 500 501 func (a *App) GetPermalinkPost(postId string, userId string) (*model.PostList, *model.AppError) { 502 if result := <-a.Srv.Store.Post().Get(postId); result.Err != nil { 503 return nil, result.Err 504 } else { 505 list := result.Data.(*model.PostList) 506 507 if len(list.Order) != 1 { 508 return nil, model.NewAppError("getPermalinkTmp", "api.post_get_post_by_id.get.app_error", nil, "", http.StatusNotFound) 509 } 510 post := list.Posts[list.Order[0]] 511 512 var channel *model.Channel 513 var err *model.AppError 514 if channel, err = a.GetChannel(post.ChannelId); err != nil { 515 return nil, err 516 } 517 518 if err = a.JoinChannel(channel, userId); err != nil { 519 return nil, err 520 } 521 522 return list, nil 523 } 524 } 525 526 func (a *App) GetPostsBeforePost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 527 if result := <-a.Srv.Store.Post().GetPostsBefore(channelId, postId, perPage, page*perPage); result.Err != nil { 528 return nil, result.Err 529 } else { 530 return result.Data.(*model.PostList), nil 531 } 532 } 533 534 func (a *App) GetPostsAfterPost(channelId, postId string, page, perPage int) (*model.PostList, *model.AppError) { 535 if result := <-a.Srv.Store.Post().GetPostsAfter(channelId, postId, perPage, page*perPage); result.Err != nil { 536 return nil, result.Err 537 } else { 538 return result.Data.(*model.PostList), nil 539 } 540 } 541 542 func (a *App) GetPostsAroundPost(postId, channelId string, offset, limit int, before bool) (*model.PostList, *model.AppError) { 543 var pchan store.StoreChannel 544 if before { 545 pchan = a.Srv.Store.Post().GetPostsBefore(channelId, postId, limit, offset) 546 } else { 547 pchan = a.Srv.Store.Post().GetPostsAfter(channelId, postId, limit, offset) 548 } 549 550 if result := <-pchan; result.Err != nil { 551 return nil, result.Err 552 } else { 553 return result.Data.(*model.PostList), nil 554 } 555 } 556 557 func (a *App) DeletePost(postId string) (*model.Post, *model.AppError) { 558 if result := <-a.Srv.Store.Post().GetSingle(postId); result.Err != nil { 559 result.Err.StatusCode = http.StatusBadRequest 560 return nil, result.Err 561 } else { 562 post := result.Data.(*model.Post) 563 564 if result := <-a.Srv.Store.Post().Delete(postId, model.GetMillis()); result.Err != nil { 565 return nil, result.Err 566 } 567 568 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_DELETED, "", post.ChannelId, "", nil) 569 message.Add("post", a.PostWithProxyAddedToImageURLs(post).ToJson()) 570 571 a.Go(func() { 572 a.Publish(message) 573 }) 574 a.Go(func() { 575 a.DeletePostFiles(post) 576 }) 577 a.Go(func() { 578 a.DeleteFlaggedPosts(post.Id) 579 }) 580 581 esInterface := a.Elasticsearch 582 if esInterface != nil && *a.Config().ElasticsearchSettings.EnableIndexing { 583 a.Go(func() { 584 esInterface.DeletePost(post) 585 }) 586 } 587 588 a.InvalidateCacheForChannelPosts(post.ChannelId) 589 590 return post, nil 591 } 592 } 593 594 func (a *App) DeleteFlaggedPosts(postId string) { 595 if result := <-a.Srv.Store.Preference().DeleteCategoryAndName(model.PREFERENCE_CATEGORY_FLAGGED_POST, postId); result.Err != nil { 596 l4g.Warn(utils.T("api.post.delete_flagged_post.app_error.warn"), result.Err) 597 return 598 } 599 } 600 601 func (a *App) DeletePostFiles(post *model.Post) { 602 if len(post.FileIds) != 0 { 603 return 604 } 605 606 if result := <-a.Srv.Store.FileInfo().DeleteForPost(post.Id); result.Err != nil { 607 l4g.Warn(utils.T("api.post.delete_post_files.app_error.warn"), post.Id, result.Err) 608 } 609 } 610 611 func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool) (*model.PostList, *model.AppError) { 612 paramsList := model.ParseSearchParams(terms) 613 614 esInterface := a.Elasticsearch 615 if license := a.License(); esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && license != nil && *license.Features.Elasticsearch { 616 finalParamsList := []*model.SearchParams{} 617 618 for _, params := range paramsList { 619 params.OrTerms = isOrSearch 620 // Don't allow users to search for "*" 621 if params.Terms != "*" { 622 // Convert channel names to channel IDs 623 for idx, channelName := range params.InChannels { 624 if channel, err := a.GetChannelByName(channelName, teamId); err != nil { 625 l4g.Error(err) 626 } else { 627 params.InChannels[idx] = channel.Id 628 } 629 } 630 631 // Convert usernames to user IDs 632 for idx, username := range params.FromUsers { 633 if user, err := a.GetUserByUsername(username); err != nil { 634 l4g.Error(err) 635 } else { 636 params.FromUsers[idx] = user.Id 637 } 638 } 639 640 finalParamsList = append(finalParamsList, params) 641 } 642 } 643 644 // If the processed search params are empty, return empty search results. 645 if len(finalParamsList) == 0 { 646 return model.NewPostList(), nil 647 } 648 649 // We only allow the user to search in channels they are a member of. 650 userChannels, err := a.GetChannelsForUser(teamId, userId) 651 if err != nil { 652 l4g.Error(err) 653 return nil, err 654 } 655 656 postIds, err := a.Elasticsearch.SearchPosts(userChannels, finalParamsList) 657 if err != nil { 658 return nil, err 659 } 660 661 // Get the posts 662 postList := model.NewPostList() 663 if len(postIds) > 0 { 664 if presult := <-a.Srv.Store.Post().GetPostsByIds(postIds); presult.Err != nil { 665 return nil, presult.Err 666 } else { 667 for _, p := range presult.Data.([]*model.Post) { 668 postList.AddPost(p) 669 postList.AddOrder(p.Id) 670 } 671 } 672 } 673 674 return postList, nil 675 } else { 676 if !*a.Config().ServiceSettings.EnablePostSearch { 677 return nil, model.NewAppError("SearchPostsInTeam", "store.sql_post.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented) 678 } 679 680 channels := []store.StoreChannel{} 681 682 for _, params := range paramsList { 683 params.OrTerms = isOrSearch 684 // don't allow users to search for everything 685 if params.Terms != "*" { 686 channels = append(channels, a.Srv.Store.Post().Search(teamId, userId, params)) 687 } 688 } 689 690 posts := model.NewPostList() 691 for _, channel := range channels { 692 if result := <-channel; result.Err != nil { 693 return nil, result.Err 694 } else { 695 data := result.Data.(*model.PostList) 696 posts.Extend(data) 697 } 698 } 699 700 posts.SortByCreateAt() 701 702 return posts, nil 703 } 704 } 705 706 func (a *App) GetFileInfosForPost(postId string, readFromMaster bool) ([]*model.FileInfo, *model.AppError) { 707 pchan := a.Srv.Store.Post().GetSingle(postId) 708 fchan := a.Srv.Store.FileInfo().GetForPost(postId, readFromMaster, true) 709 710 var infos []*model.FileInfo 711 if result := <-fchan; result.Err != nil { 712 return nil, result.Err 713 } else { 714 infos = result.Data.([]*model.FileInfo) 715 } 716 717 if len(infos) == 0 { 718 // No FileInfos were returned so check if they need to be created for this post 719 var post *model.Post 720 if result := <-pchan; result.Err != nil { 721 return nil, result.Err 722 } else { 723 post = result.Data.(*model.Post) 724 } 725 726 if len(post.Filenames) > 0 { 727 a.Srv.Store.FileInfo().InvalidateFileInfosForPostCache(postId) 728 // The post has Filenames that need to be replaced with FileInfos 729 infos = a.MigrateFilenamesToFileInfos(post) 730 } 731 } 732 733 return infos, nil 734 } 735 736 func (a *App) GetOpenGraphMetadata(requestURL string) *opengraph.OpenGraph { 737 og := opengraph.NewOpenGraph() 738 739 res, err := a.HTTPClient(false).Get(requestURL) 740 if err != nil { 741 l4g.Error("GetOpenGraphMetadata request failed for url=%v with err=%v", requestURL, err.Error()) 742 return og 743 } 744 defer consumeAndClose(res) 745 746 if err := og.ProcessHTML(res.Body); err != nil { 747 l4g.Error("GetOpenGraphMetadata processing failed for url=%v with err=%v", requestURL, err.Error()) 748 } 749 750 makeOpenGraphURLsAbsolute(og, requestURL) 751 752 return og 753 } 754 755 func makeOpenGraphURLsAbsolute(og *opengraph.OpenGraph, requestURL string) { 756 parsedRequestURL, err := url.Parse(requestURL) 757 if err != nil { 758 l4g.Warn("makeOpenGraphURLsAbsolute failed to parse url=%v", requestURL) 759 return 760 } 761 762 makeURLAbsolute := func(resultURL string) string { 763 if resultURL == "" { 764 return resultURL 765 } 766 767 parsedResultURL, err := url.Parse(resultURL) 768 if err != nil { 769 l4g.Warn("makeOpenGraphURLsAbsolute failed to parse result url=%v", resultURL) 770 return resultURL 771 } 772 773 if parsedResultURL.IsAbs() { 774 return resultURL 775 } 776 777 return parsedRequestURL.ResolveReference(parsedResultURL).String() 778 } 779 780 og.URL = makeURLAbsolute(og.URL) 781 782 for _, image := range og.Images { 783 image.URL = makeURLAbsolute(image.URL) 784 image.SecureURL = makeURLAbsolute(image.SecureURL) 785 } 786 787 for _, audio := range og.Audios { 788 audio.URL = makeURLAbsolute(audio.URL) 789 audio.SecureURL = makeURLAbsolute(audio.SecureURL) 790 } 791 792 for _, video := range og.Videos { 793 video.URL = makeURLAbsolute(video.URL) 794 video.SecureURL = makeURLAbsolute(video.SecureURL) 795 } 796 } 797 798 func (a *App) DoPostAction(postId string, actionId string, userId string) *model.AppError { 799 pchan := a.Srv.Store.Post().GetSingle(postId) 800 801 var post *model.Post 802 if result := <-pchan; result.Err != nil { 803 return result.Err 804 } else { 805 post = result.Data.(*model.Post) 806 } 807 808 action := post.GetAction(actionId) 809 if action == nil || action.Integration == nil { 810 return model.NewAppError("DoPostAction", "api.post.do_action.action_id.app_error", nil, fmt.Sprintf("action=%v", action), http.StatusNotFound) 811 } 812 813 request := &model.PostActionIntegrationRequest{ 814 UserId: userId, 815 Context: action.Integration.Context, 816 } 817 818 req, _ := http.NewRequest("POST", action.Integration.URL, strings.NewReader(request.ToJson())) 819 req.Header.Set("Content-Type", "application/json") 820 req.Header.Set("Accept", "application/json") 821 resp, err := a.HTTPClient(false).Do(req) 822 if err != nil { 823 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 824 } 825 defer consumeAndClose(resp) 826 827 if resp.StatusCode != http.StatusOK { 828 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, fmt.Sprintf("status=%v", resp.StatusCode), http.StatusBadRequest) 829 } 830 831 var response model.PostActionIntegrationResponse 832 if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { 833 return model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "err="+err.Error(), http.StatusBadRequest) 834 } 835 836 retainedProps := []string{"override_username", "override_icon_url"} 837 838 if response.Update != nil { 839 response.Update.Id = postId 840 response.Update.AddProp("from_webhook", "true") 841 for _, prop := range retainedProps { 842 if value, ok := post.Props[prop]; ok { 843 response.Update.Props[prop] = value 844 } else { 845 delete(response.Update.Props, prop) 846 } 847 } 848 if _, err := a.UpdatePost(response.Update, false); err != nil { 849 return err 850 } 851 } 852 853 if response.EphemeralText != "" { 854 ephemeralPost := &model.Post{} 855 ephemeralPost.Message = parseSlackLinksToMarkdown(response.EphemeralText) 856 ephemeralPost.ChannelId = post.ChannelId 857 ephemeralPost.RootId = post.RootId 858 if ephemeralPost.RootId == "" { 859 ephemeralPost.RootId = post.Id 860 } 861 ephemeralPost.UserId = post.UserId 862 ephemeralPost.AddProp("from_webhook", "true") 863 for _, prop := range retainedProps { 864 if value, ok := post.Props[prop]; ok { 865 ephemeralPost.Props[prop] = value 866 } else { 867 delete(ephemeralPost.Props, prop) 868 } 869 } 870 a.SendEphemeralPost(userId, ephemeralPost) 871 } 872 873 return nil 874 } 875 876 func (a *App) PostListWithProxyAddedToImageURLs(list *model.PostList) *model.PostList { 877 if f := a.ImageProxyAdder(); f != nil { 878 return list.WithRewrittenImageURLs(f) 879 } 880 return list 881 } 882 883 func (a *App) PostWithProxyAddedToImageURLs(post *model.Post) *model.Post { 884 if f := a.ImageProxyAdder(); f != nil { 885 return post.WithRewrittenImageURLs(f) 886 } 887 return post 888 } 889 890 func (a *App) PostWithProxyRemovedFromImageURLs(post *model.Post) *model.Post { 891 if f := a.ImageProxyRemover(); f != nil { 892 return post.WithRewrittenImageURLs(f) 893 } 894 return post 895 } 896 897 func (a *App) PostPatchWithProxyRemovedFromImageURLs(patch *model.PostPatch) *model.PostPatch { 898 if f := a.ImageProxyRemover(); f != nil { 899 return patch.WithRewrittenImageURLs(f) 900 } 901 return patch 902 } 903 904 func (a *App) imageProxyConfig() (proxyType, proxyURL, options, siteURL string) { 905 cfg := a.Config() 906 907 if cfg.ServiceSettings.ImageProxyURL == nil || cfg.ServiceSettings.ImageProxyType == nil || cfg.ServiceSettings.SiteURL == nil { 908 return 909 } 910 911 proxyURL = *cfg.ServiceSettings.ImageProxyURL 912 proxyType = *cfg.ServiceSettings.ImageProxyType 913 siteURL = *cfg.ServiceSettings.SiteURL 914 915 if proxyURL == "" || proxyType == "" { 916 return "", "", "", "" 917 } 918 919 if proxyURL[len(proxyURL)-1] != '/' { 920 proxyURL += "/" 921 } 922 923 if siteURL == "" || siteURL[len(siteURL)-1] != '/' { 924 siteURL += "/" 925 } 926 927 if cfg.ServiceSettings.ImageProxyOptions != nil { 928 options = *cfg.ServiceSettings.ImageProxyOptions 929 } 930 931 return 932 } 933 934 func (a *App) ImageProxyAdder() func(string) string { 935 proxyType, proxyURL, options, siteURL := a.imageProxyConfig() 936 if proxyType == "" { 937 return nil 938 } 939 940 return func(url string) string { 941 if url == "" || url[0] == '/' || strings.HasPrefix(url, siteURL) || strings.HasPrefix(url, proxyURL) { 942 return url 943 } 944 945 switch proxyType { 946 case "atmos/camo": 947 mac := hmac.New(sha1.New, []byte(options)) 948 mac.Write([]byte(url)) 949 digest := hex.EncodeToString(mac.Sum(nil)) 950 return proxyURL + digest + "/" + hex.EncodeToString([]byte(url)) 951 } 952 953 return url 954 } 955 } 956 957 func (a *App) ImageProxyRemover() (f func(string) string) { 958 proxyType, proxyURL, _, _ := a.imageProxyConfig() 959 if proxyType == "" { 960 return nil 961 } 962 963 return func(url string) string { 964 switch proxyType { 965 case "atmos/camo": 966 if strings.HasPrefix(url, proxyURL) { 967 if slash := strings.IndexByte(url[len(proxyURL):], '/'); slash >= 0 { 968 if decoded, err := hex.DecodeString(url[len(proxyURL)+slash+1:]); err == nil { 969 return string(decoded) 970 } 971 } 972 } 973 } 974 975 return url 976 } 977 }