From 701c8ad3dae9b27c00cf12064a997a9be9ccd400 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Wed, 3 May 2023 15:56:21 -0300 Subject: [PATCH 1/8] Add presigned url --- handlers/facebookapp/facebookapp.go | 66 ++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 7ef0b5d97..f53d7cf26 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -14,6 +14,10 @@ import ( "strings" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" @@ -1318,10 +1322,16 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if err != nil { return status, err } + + urlStr, err := PresignedURL(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + if err != nil { + return status, err + } + if attType == "application" { attType = "document" } - media := wacMTMedia{Link: parsedURL.String()} + media := wacMTMedia{Link: urlStr} if attType == "image" { header.Params = append(header.Params, &wacParam{Type: "image", Image: &media}) } else if attType == "video" { @@ -1418,11 +1428,17 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if err != nil { return status, err } + + urlStr, err := PresignedURL(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + if err != nil { + return status, err + } + if attType == "application" { attType = "document" } payload.Type = attType - media := wacMTMedia{Link: parsedURL.String()} + media := wacMTMedia{Link: urlStr} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] hasCaption = true @@ -1454,12 +1470,18 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if len(msg.Attachments()) > 0 { attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) attType = strings.Split(attType, "/")[0] + + urlStr, err := PresignedURL(attURL, h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + if err != nil { + return status, err + } + if attType == "application" { attType = "document" } if attType == "image" { image := wacMTMedia{ - Link: attURL, + Link: urlStr, } interactive.Header = &struct { Type string "json:\"type\"" @@ -1470,7 +1492,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) }{Type: "image", Image: image} } else if attType == "video" { video := wacMTMedia{ - Link: attURL, + Link: urlStr, } interactive.Header = &struct { Type string "json:\"type\"" @@ -1485,7 +1507,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return nil, err } document := wacMTMedia{ - Link: attURL, + Link: urlStr, Filename: filename, } interactive.Header = &struct { @@ -1500,7 +1522,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if i == 0 { zeroIndex = true } - payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: attURL}} + payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: urlStr}} status, _, err := requestWAC(payloadAudio, accessToken, msg, status, wacPhoneURL, zeroIndex) if err != nil { return status, nil @@ -1770,6 +1792,38 @@ func (h *handler) getTemplate(msg courier.Msg) (*MsgTemplating, error) { return templating, err } +func PresignedURL(link string, accessKey string, secretKey string, region string) (string, error) { + + splitURL := strings.Split(link, ".") + bucketName := strings.TrimPrefix(splitURL[0], "http://") + + splitURL = strings.Split(link, "attachments") + objectKey := "/attachments" + splitURL[1] + + sess, err := session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), + Region: aws.String(region), + }) + if err != nil { + return "", err + } + + svc := s3.New(sess) + + req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }) + urlStr, err := req.Presign((24 * time.Hour) * 7) + + if err != nil { + return "", err + } + + return urlStr, nil + +} + type TemplateMetadata struct { Templating *MsgTemplating `json:"templating"` } From 9a9f7ffee1fccf88e0d1f8e652fd80d439c358f1 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Wed, 3 May 2023 17:17:43 -0300 Subject: [PATCH 2/8] Fix prefix string --- handlers/facebookapp/facebookapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index f53d7cf26..891feb8bf 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -1795,7 +1795,7 @@ func (h *handler) getTemplate(msg courier.Msg) (*MsgTemplating, error) { func PresignedURL(link string, accessKey string, secretKey string, region string) (string, error) { splitURL := strings.Split(link, ".") - bucketName := strings.TrimPrefix(splitURL[0], "http://") + bucketName := strings.TrimPrefix(splitURL[0], "https://") splitURL = strings.Split(link, "attachments") objectKey := "/attachments" + splitURL[1] From a3b64c9a6cd9e7d0d2c9f3a47b3f4848178af3c5 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 5 May 2023 17:52:27 -0300 Subject: [PATCH 3/8] Change media tests to use presignedURL --- config.go | 4 +- handlers/facebookapp/facebookapp.go | 12 +++-- handlers/facebookapp/facebookapp_test.go | 61 +++++++++++++++--------- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/config.go b/config.go index c91ce19ea..88c55a3a2 100644 --- a/config.go +++ b/config.go @@ -57,8 +57,8 @@ func NewConfig() *Config { S3MediaPrefix: "/media/", S3DisableSSL: false, S3ForcePathStyle: false, - AWSAccessKeyID: "", - AWSSecretAccessKey: "", + AWSAccessKeyID: "missing_aws_access_key_id", + AWSSecretAccessKey: "missing_secret_key_id", FacebookApplicationSecret: "missing_facebook_app_secret", FacebookWebhookSecret: "missing_facebook_webhook_secret", WhatsappAdminSystemUserToken: "missing_whatsapp_admin_system_user_token", diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index 891feb8bf..e4f7cbb89 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -53,6 +53,8 @@ var ( "account": "ACCOUNT_UPDATE", "agent": "HUMAN_AGENT", } + + presignedURLFunc func(string, string, string, string) (string, error) ) // keys for extra in channel events @@ -84,7 +86,7 @@ func init() { courier.RegisterHandler(newHandler("IG", "Instagram", false)) courier.RegisterHandler(newHandler("FBA", "Facebook", false)) courier.RegisterHandler(newHandler("WAC", "WhatsApp Cloud", false)) - + presignedURLFunc = PresignedURL } type handler struct { @@ -1323,7 +1325,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return status, err } - urlStr, err := PresignedURL(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + urlStr, err := presignedURLFunc(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) if err != nil { return status, err } @@ -1429,7 +1431,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return status, err } - urlStr, err := PresignedURL(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + urlStr, err := presignedURLFunc(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) if err != nil { return status, err } @@ -1471,7 +1473,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) attType = strings.Split(attType, "/")[0] - urlStr, err := PresignedURL(attURL, h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) + urlStr, err := presignedURLFunc(attURL, h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) if err != nil { return status, err } @@ -1798,7 +1800,7 @@ func PresignedURL(link string, accessKey string, secretKey string, region string bucketName := strings.TrimPrefix(splitURL[0], "https://") splitURL = strings.Split(link, "attachments") - objectKey := "/attachments" + splitURL[1] + objectKey := "/attachments" + splitURL[0] sess, err := session.NewSession(&aws.Config{ Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 26c43c18e..3633b944c 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -384,6 +384,7 @@ func TestVerify(t *testing.T) { func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { sendURL = s.URL graphURL = s.URL + presignedURLFunc = PresignedURLMock } var SendTestCasesFBA = []ChannelSendTestCase{ @@ -532,12 +533,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "audio caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, + Attachments: []string{"audio/mpeg:https://foo.bar/attachments/audio.mp3"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/attachments/audio.mp3"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -556,25 +557,25 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "document caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"application/pdf:https://foo.bar/document.pdf"}, ResponseStatus: 201, + Attachments: []string{"application/pdf:https://foo.bar/attachments/document.pdf"}, ResponseStatus: 201, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/attachments/document.pdf","caption":"document caption","filename":"document.pdf"}}`, SendPrep: setSendURL}, {Label: "Image Send", Text: "image caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg","caption":"image caption"}}`, SendPrep: setSendURL}, {Label: "Video Send", Text: "video caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + Attachments: []string{"video/mp4:https://foo.bar/attachments/video.mp4"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/attachments/video.mp4","caption":"video caption"}}`, SendPrep: setSendURL}, {Label: "Template Send", Text: "templated message", @@ -614,19 +615,19 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Interactive Button Message Send with attachment", Text: "Interactive Button Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"BUTTON1"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","video":{},"image":{"link":"https://foo.bar/image.jpg"},"document":{}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","video":{},"image":{"link":"https://foo.bar/attachments/image.jpg"},"document":{}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, SendPrep: setSendURL}, {Label: "Interactive List Message Send with attachment", Text: "Interactive List Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -644,12 +645,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Interactive Button Message Send with audio attachment", Text: "Interactive Button Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"BUTTON0", "BUTTON1", "BUTTON2"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, + Attachments: []string{"audio/mp3:https://foo.bar/attachments/audio.mp3"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/attachments/audio.mp3"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -668,25 +669,25 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"image","image":{"link":"https://foo.bar/image.jpg"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"image","image":{"link":"https://foo.bar/attachments/image.jpg"}}]}]}}`, SendPrep: setSendURL}, {Label: "Media Message Template Send - Video", Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + Attachments: []string{"video/mp4:https://foo.bar/attachments/video.mp4"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"video","video":{"link":"https://foo.bar/video.mp4"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"video","video":{"link":"https://foo.bar/attachments/video.mp4"}}]}]}}`, SendPrep: setSendURL}, {Label: "Media Message Template Send - Document", Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + Attachments: []string{"application/pdf:https://foo.bar/attachments/document.pdf"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"document","document":{"link":"https://foo.bar/document.pdf","filename":"document.pdf"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"document","document":{"link":"https://foo.bar/attachments/document.pdf","filename":"document.pdf"}}]}]}}`, SendPrep: setSendURL}, {Label: "Link Sending", Text: "Link Sending https://link.com", URN: "whatsapp:250788123123", Path: "/12345_ID/messages", @@ -704,9 +705,9 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Attachment with Caption", Text: "Simple Message", URN: "whatsapp:5511987654321", Path: "/12345_ID/messages", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"5511987654321","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"Simple Message"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"5511987654321","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg","caption":"Simple Message"}}`, SendPrep: setSendURL, NewURN: "whatsapp:551187654321"}, } @@ -745,3 +746,19 @@ func TestSigning(t *testing.T) { assert.Equal(t, tc.Signature, sig, "%d: mismatched signature", i) } } + +func PresignedURLMock(link string, a string, b string, c string) (string, error) { + + if strings.HasSuffix(link, ".mp3") { + return "https://foo.bar/attachments/audio.mp3", nil + } else if strings.HasSuffix(link, ".pdf") { + return "https://foo.bar/attachments/document.pdf", nil + } else if strings.HasSuffix(link, ".jpg") { + return "https://foo.bar/attachments/image.jpg", nil + } else if strings.HasSuffix(link, ".mp4") { + return "https://foo.bar/attachments/video.mp4", nil + } + + return "", fmt.Errorf("no match") + +} From d4208c0494c521928095fb5f9e7fe374ac5ea03e Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 5 May 2023 16:29:25 -0300 Subject: [PATCH 4/8] Fix test TestMsgSuite/TestWriteAttachment --- backends/rapidpro/backend_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index c84f5c8b1..d5e7faf8a 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -962,14 +962,14 @@ func (ts *BackendTestSuite) TestWriteAttachment() { content := "" switch r.URL.Path { case "/test.jpg": - content = "malformedjpegbody" + content = "\xFF\xD8\xFF" case "/giffy": content = "GIF87aandstuff" case "/header": w.Header().Add("Content-Type", "image/png") - content = "nothingbody" + content = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" default: content = "unknown" From 62a5737e3d1b1388db7c0052caa309b53a2e4896 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 26 May 2023 15:45:43 -0300 Subject: [PATCH 5/8] Add presignedURL to sender.go --- backends/rapidpro/msg.go | 5 +++++ msg.go | 1 + presigned_url.go | 43 ++++++++++++++++++++++++++++++++++++++++ sender.go | 10 ++++++++++ 4 files changed, 59 insertions(+) create mode 100644 presigned_url.go diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 341395563..f9ba55cf8 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -642,3 +642,8 @@ func (m *DBMsg) WithURNAuth(auth string) courier.Msg { m.URNAuth_ = auth return m } + +func (m *DBMsg) WithPresignedURL(urls []string) courier.Msg { + m.Attachments_ = urls + return m +} diff --git a/msg.go b/msg.go index c94dd6491..79111185a 100644 --- a/msg.go +++ b/msg.go @@ -123,6 +123,7 @@ type Msg interface { WithURNAuth(auth string) Msg WithMetadata(metadata json.RawMessage) Msg WithFlow(flow *FlowReference) Msg + WithPresignedURL(urls []string) Msg EventID() int64 SessionStatus() string diff --git a/presigned_url.go b/presigned_url.go new file mode 100644 index 000000000..0c923bf94 --- /dev/null +++ b/presigned_url.go @@ -0,0 +1,43 @@ +package courier + +import ( + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" +) + +func PresignedURL(link string, accessKey string, secretKey string, region string) (string, error) { + + splitURL := strings.Split(link, ".") + bucketName := strings.TrimPrefix(splitURL[0], "https://") + + splitURL = strings.Split(link, "attachments") + objectKey := "/attachments" + splitURL[0] + + sess, err := session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), + Region: aws.String(region), + }) + if err != nil { + return "", err + } + + svc := s3.New(sess) + + req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }) + urlStr, err := req.Presign((24 * time.Hour) * 7) + + if err != nil { + return "", err + } + + return urlStr, nil + +} diff --git a/sender.go b/sender.go index 094e1b437..c01915f6d 100644 --- a/sender.go +++ b/sender.go @@ -164,7 +164,17 @@ func (w *Sender) sendMessage(msg Msg) { log = log.WithField("msg_id", msg.ID().String()).WithField("msg_text", msg.Text()).WithField("msg_urn", msg.URN().Identity()) if len(msg.Attachments()) > 0 { + var attachments []string log = log.WithField("attachments", msg.Attachments()) + for _, att := range msg.Attachments() { + url, err := PresignedURL(att, server.Config().AWSAccessKeyID, server.Config().AWSSecretAccessKey, server.Config().S3Region) + if err != nil { + log.WithError(err).Error("error converting attachment for pre-signed url") + } + attachments = append(attachments, url) + } + msg = msg.WithPresignedURL(attachments) + } if len(msg.QuickReplies()) > 0 { log = log.WithField("quick_replies", msg.QuickReplies()) From 299ee5288bd89d20dabbbc1a91a540f4da9cd163 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Fri, 26 May 2023 16:17:52 -0300 Subject: [PATCH 6/8] Add presigned url to sender.go --- handlers/facebookapp/facebookapp.go | 64 +++-------------------------- test.go | 5 +++ 2 files changed, 11 insertions(+), 58 deletions(-) diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index e4f7cbb89..e31a74a0a 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -14,10 +14,6 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/s3" "github.com/buger/jsonparser" "github.com/nyaruka/courier" "github.com/nyaruka/courier/handlers" @@ -86,7 +82,6 @@ func init() { courier.RegisterHandler(newHandler("IG", "Instagram", false)) courier.RegisterHandler(newHandler("FBA", "Facebook", false)) courier.RegisterHandler(newHandler("WAC", "WhatsApp Cloud", false)) - presignedURLFunc = PresignedURL } type handler struct { @@ -1325,15 +1320,10 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return status, err } - urlStr, err := presignedURLFunc(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) - if err != nil { - return status, err - } - if attType == "application" { attType = "document" } - media := wacMTMedia{Link: urlStr} + media := wacMTMedia{Link: parsedURL.String()} if attType == "image" { header.Params = append(header.Params, &wacParam{Type: "image", Image: &media}) } else if attType == "video" { @@ -1431,16 +1421,11 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return status, err } - urlStr, err := presignedURLFunc(parsedURL.String(), h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) - if err != nil { - return status, err - } - if attType == "application" { attType = "document" } payload.Type = attType - media := wacMTMedia{Link: urlStr} + media := wacMTMedia{Link: parsedURL.String()} if len(msgParts) == 1 && attType != "audio" && len(msg.Attachments()) == 1 && len(msg.QuickReplies()) == 0 { media.Caption = msgParts[i] hasCaption = true @@ -1473,17 +1458,12 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) attType = strings.Split(attType, "/")[0] - urlStr, err := presignedURLFunc(attURL, h.Server().Config().AWSAccessKeyID, h.Server().Config().AWSSecretAccessKey, h.Server().Config().S3Region) - if err != nil { - return status, err - } - if attType == "application" { attType = "document" } if attType == "image" { image := wacMTMedia{ - Link: urlStr, + Link: attURL, } interactive.Header = &struct { Type string "json:\"type\"" @@ -1494,7 +1474,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) }{Type: "image", Image: image} } else if attType == "video" { video := wacMTMedia{ - Link: urlStr, + Link: attURL, } interactive.Header = &struct { Type string "json:\"type\"" @@ -1509,7 +1489,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) return nil, err } document := wacMTMedia{ - Link: urlStr, + Link: attURL, Filename: filename, } interactive.Header = &struct { @@ -1524,7 +1504,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if i == 0 { zeroIndex = true } - payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: urlStr}} + payloadAudio = wacMTPayload{MessagingProduct: "whatsapp", RecipientType: "individual", To: msg.URN().Path(), Type: "audio", Audio: &wacMTMedia{Link: attURL}} status, _, err := requestWAC(payloadAudio, accessToken, msg, status, wacPhoneURL, zeroIndex) if err != nil { return status, nil @@ -1794,38 +1774,6 @@ func (h *handler) getTemplate(msg courier.Msg) (*MsgTemplating, error) { return templating, err } -func PresignedURL(link string, accessKey string, secretKey string, region string) (string, error) { - - splitURL := strings.Split(link, ".") - bucketName := strings.TrimPrefix(splitURL[0], "https://") - - splitURL = strings.Split(link, "attachments") - objectKey := "/attachments" + splitURL[0] - - sess, err := session.NewSession(&aws.Config{ - Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), - Region: aws.String(region), - }) - if err != nil { - return "", err - } - - svc := s3.New(sess) - - req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ - Bucket: aws.String(bucketName), - Key: aws.String(objectKey), - }) - urlStr, err := req.Presign((24 * time.Hour) * 7) - - if err != nil { - return "", err - } - - return urlStr, nil - -} - type TemplateMetadata struct { Templating *MsgTemplating `json:"templating"` } diff --git a/test.go b/test.go index 86240b5df..8c7a182ec 100644 --- a/test.go +++ b/test.go @@ -628,6 +628,11 @@ func (m *mockMsg) WithAttachment(url string) Msg { func (m *mockMsg) WithMetadata(metadata json.RawMessage) Msg { m.metadata = metadata; return m } func (m *mockMsg) WithFlow(flow *FlowReference) Msg { m.flow = flow; return m } +func (m *mockMsg) WithPresignedURL(urls []string) Msg { + m.attachments = urls + return m +} +func (m *mockMsg) Status() MsgStatusValue { return "" } //----------------------------------------------------------------------------- // Mock status implementation From 1edbe1ac3cbb8cbd63b41c657d7030da5f529d05 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Mon, 19 Jun 2023 17:17:03 -0300 Subject: [PATCH 7/8] Fix presigned urls --- handlers/facebookapp/facebookapp.go | 2 -- handlers/facebookapp/facebookapp_test.go | 17 ----------------- presigned_url.go | 11 ++++++++++- sender.go | 6 ++++-- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index e31a74a0a..0adc7b78c 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -49,8 +49,6 @@ var ( "account": "ACCOUNT_UPDATE", "agent": "HUMAN_AGENT", } - - presignedURLFunc func(string, string, string, string) (string, error) ) // keys for extra in channel events diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 3633b944c..1f7db7561 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -384,7 +384,6 @@ func TestVerify(t *testing.T) { func setSendURL(s *httptest.Server, h courier.ChannelHandler, c courier.Channel, m courier.Msg) { sendURL = s.URL graphURL = s.URL - presignedURLFunc = PresignedURLMock } var SendTestCasesFBA = []ChannelSendTestCase{ @@ -746,19 +745,3 @@ func TestSigning(t *testing.T) { assert.Equal(t, tc.Signature, sig, "%d: mismatched signature", i) } } - -func PresignedURLMock(link string, a string, b string, c string) (string, error) { - - if strings.HasSuffix(link, ".mp3") { - return "https://foo.bar/attachments/audio.mp3", nil - } else if strings.HasSuffix(link, ".pdf") { - return "https://foo.bar/attachments/document.pdf", nil - } else if strings.HasSuffix(link, ".jpg") { - return "https://foo.bar/attachments/image.jpg", nil - } else if strings.HasSuffix(link, ".mp4") { - return "https://foo.bar/attachments/video.mp4", nil - } - - return "", fmt.Errorf("no match") - -} diff --git a/presigned_url.go b/presigned_url.go index 0c923bf94..fd1bc498d 100644 --- a/presigned_url.go +++ b/presigned_url.go @@ -16,7 +16,7 @@ func PresignedURL(link string, accessKey string, secretKey string, region string bucketName := strings.TrimPrefix(splitURL[0], "https://") splitURL = strings.Split(link, "attachments") - objectKey := "/attachments" + splitURL[0] + objectKey := "/attachments" + splitURL[1] sess, err := session.NewSession(&aws.Config{ Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), @@ -41,3 +41,12 @@ func PresignedURL(link string, accessKey string, secretKey string, region string return urlStr, nil } + +// SplitAttachment takes an attachment string and returns the media type and URL for the attachment +func SplitAttachment(attachment string) (string, string) { + parts := strings.SplitN(attachment, ":", 2) + if len(parts) < 2 { + return "", parts[0] + } + return parts[0], parts[1] +} diff --git a/sender.go b/sender.go index c01915f6d..21d22e1b3 100644 --- a/sender.go +++ b/sender.go @@ -167,11 +167,13 @@ func (w *Sender) sendMessage(msg Msg) { var attachments []string log = log.WithField("attachments", msg.Attachments()) for _, att := range msg.Attachments() { - url, err := PresignedURL(att, server.Config().AWSAccessKeyID, server.Config().AWSSecretAccessKey, server.Config().S3Region) + attType, attURL := SplitAttachment(att) + url, err := PresignedURL(attURL, server.Config().AWSAccessKeyID, server.Config().AWSSecretAccessKey, server.Config().S3Region) if err != nil { log.WithError(err).Error("error converting attachment for pre-signed url") } - attachments = append(attachments, url) + att = attType + ":" + url + attachments = append(attachments, att) } msg = msg.WithPresignedURL(attachments) From 57c9008e6aa4cb0d3b452295d55bcfd2ca7eef96 Mon Sep 17 00:00:00 2001 From: Robi9 Date: Mon, 19 Jun 2023 17:48:46 -0300 Subject: [PATCH 8/8] remove unnecessary interface --- test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test.go b/test.go index 8c7a182ec..e468ce969 100644 --- a/test.go +++ b/test.go @@ -632,7 +632,6 @@ func (m *mockMsg) WithPresignedURL(urls []string) Msg { m.attachments = urls return m } -func (m *mockMsg) Status() MsgStatusValue { return "" } //----------------------------------------------------------------------------- // Mock status implementation