Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions channel_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
EventTypeWelcomeMessage ChannelEventType = "welcome_message"
EventTypeOptIn ChannelEventType = "optin"
EventTypeOptOut ChannelEventType = "optout"
EventDeleteContact ChannelEventType = "delete_contact"
)

//-----------------------------------------------------------------------------
Expand Down
48 changes: 48 additions & 0 deletions handlers/meta/facebook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,54 @@ func TestFacebookDescribeURN(t *testing.T) {
AssertChannelLogRedaction(t, clog, []string{"a123", "wac_admin_system_user_token"})
}

func TestDeleteRequest(t *testing.T) {
urn, _ := urns.New(urns.Facebook, "218471")
RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), []IncomingTestCase{
{
Label: "Receive Delete request FBA",
URL: "/c/fba/delete",
Data: `{"algorithm":"HMAC-SHA256","expires":1291840400,"issued_at":1291836800,"user_id":"218471"}`,
PrepRequest: addValidSignature,
ExistingDBURNs: []urns.URN{urn},

ExpectedRespStatus: 200,
ExpectedBodyContains: "Deletion Request Received",
NoQueueErrorCheck: true,
NoInvalidChannelCheck: true,
NoLogsExpected: true,
ExpectedEvents: []ExpectedEvent{
{Type: courier.EventDeleteContact, URN: "facebook:218471", Extra: map[string]string{"userID": "218471"}},
},
},
{
Label: "Receive Delete request FBA, contact not existing",
URL: "/c/fba/delete",
Data: `{"algorithm":"HMAC-SHA256","expires":1291840400,"issued_at":1291836800,"user_id":"123456"}`,
PrepRequest: addValidSignature,
ExistingDBURNs: []urns.URN{urn},

ExpectedRespStatus: 200,
ExpectedBodyContains: "ignoring request, no existing contact matched",
NoQueueErrorCheck: true,
NoInvalidChannelCheck: true,
NoLogsExpected: true,
},
{
Label: "Receive Delete request FBA, invalid facebook ID",
URL: "/c/fba/delete",
Data: `{"algorithm":"HMAC-SHA256","expires":1291840400,"issued_at":1291836800,"user_id":"abc1234"}`,
PrepRequest: addValidSignature,
ExistingDBURNs: []urns.URN{urn},

ExpectedRespStatus: 200,
ExpectedBodyContains: "invalid facebook id",
NoQueueErrorCheck: true,
NoInvalidChannelCheck: true,
NoLogsExpected: true,
},
})
}

func TestFacebookVerify(t *testing.T) {
RunIncomingTestCases(t, facebookTestChannels, newHandler("FBA", "Facebook"), []IncomingTestCase{
{
Expand Down
52 changes: 51 additions & 1 deletion handlers/meta/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (h *handler) Initialize(s courier.Server) error {
h.SetServer(s)
s.AddHandlerRoute(h, http.MethodGet, "receive", courier.ChannelLogTypeWebhookVerify, h.receiveVerify)
s.AddHandlerRoute(h, http.MethodPost, "receive", courier.ChannelLogTypeMultiReceive, handlers.JSONPayload(h, h.receiveEvents))
s.AddHandlerRoute(h, http.MethodPost, "delete", courier.ChannelLogTypeEventReceive, handlers.JSONPayload(h, h.deleteContactEvents))
return nil
}

Expand Down Expand Up @@ -130,7 +131,7 @@ func (h *handler) WriteRequestError(ctx context.Context, w http.ResponseWriter,

// GetChannel returns the channel
func (h *handler) GetChannel(ctx context.Context, r *http.Request) (courier.Channel, error) {
if r.Method == http.MethodGet {
if r.Method == http.MethodGet || r.URL.Path == "/c/fba/delete" {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@norkans7 I'm not getting why we need this..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use the same URL for all channels and this lookup the channel for the message however for the delete request we do not get info about the channel at all to be able to look it up so will ignore and the URN we get will be unique since they page scoped

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but how can we look up a URN without a channel? Don't we need channel to get org_id ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this will not work without a valid channel, the request I expect we get on that also cannot allow us to lookup the channel

I guess we remove the check for the contacts exists in the DB and rely on the URN that we know if page scoped

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't create a channel event without a channel... and we don't gain much from making it mailroom's problem to determine if a URN exists - we don't have an index on URN without org id. Argh.

return nil, nil
}

Expand Down Expand Up @@ -214,6 +215,55 @@ func (h *handler) resolveMediaURL(mediaID string, token string, clog *courier.Ch
return mediaURL, err
}

type DeletionRequestData struct {
Algorithm string `json:"algorithm"`
Expires int64 `json:"expires"`
IssuedAt int64 `json:"issued_at"`
UserID string `json:"user_id"`
}

type DeleteConfirmationData struct {
URL string `json:"url"`
ConfirmationCode string `json:"confirmation_code"`
}

// deleteContactEvents is our HTTP handler function for deleting data requests
func (h *handler) deleteContactEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *DeletionRequestData, clog *courier.ChannelLog) ([]courier.Event, error) {
err := h.validateSignature(r)
if err != nil {
return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, err)
}

urn, err := urns.New(urns.Facebook, payload.UserID)
if err != nil {
return nil, handlers.WriteAndLogRequestError(ctx, h, channel, w, r, errors.New("invalid facebook id"))
}

contact, err := h.Server().Backend().GetContact(ctx, channel, urn, nil, "", false, clog)
if contact == nil {
return nil, handlers.WriteAndLogRequestIgnored(ctx, h, channel, w, r, "ignoring request, no existing contact matched")
}

date := parseTimestamp(payload.IssuedAt)

events := make([]courier.Event, 0, 2)
data := make([]any, 0, 2)

event := h.Backend().NewChannelEvent(channel, courier.EventDeleteContact, urn, clog).WithOccurredOn(date).WithExtra(map[string]string{"userID": payload.UserID})

err = h.Backend().WriteChannelEvent(ctx, event, clog)
if err != nil {
return nil, err
}

confirmationURL := fmt.Sprintf("https://%s/public/forgetme/", h.Server().Config().Domain)

events = append(events, event)
data = append(data, DeleteConfirmationData{URL: confirmationURL, ConfirmationCode: string(event.UUID())})

return events, courier.WriteDataResponse(w, http.StatusOK, "Deletion Request Received", data)
}

// receiveEvents is our HTTP handler function for incoming messages and status updates
func (h *handler) receiveEvents(ctx context.Context, channel courier.Channel, w http.ResponseWriter, r *http.Request, payload *Notifications, clog *courier.ChannelLog) ([]courier.Event, error) {
err := h.validateSignature(r)
Expand Down
7 changes: 7 additions & 0 deletions handlers/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type IncomingTestCase struct {
NoInvalidChannelCheck bool
PrepRequest RequestPrepFunc

ExistingDBURNs []urns.URN

URL string
Data string
Headers map[string]string
Expand Down Expand Up @@ -159,6 +161,11 @@ func RunIncomingTestCases(t *testing.T, channels []courier.Channel, handler cour
handler.Initialize(s)

for _, tc := range testCases {
for _, urn := range tc.ExistingDBURNs {
ctx, _ := context.WithTimeout(context.Background(), time.Second*10)
s.Backend().GetContact(ctx, channels[0], urn, nil, "", true, nil)
}

t.Run(tc.Label, func(t *testing.T) {
require := require.New(t)

Expand Down