Handle inbound email integration alert getting multiple recipients (#4655)

Related to https://github.com/grafana/oncall-private/issues/2683
(when using mailgun backend, you can get multiple recipients, keep the
first one matching the domain; other backends seem to just return the
first one)
This commit is contained in:
Matias Bordese 2024-07-11 09:51:01 -03:00 committed by GitHub
parent 997167fcbe
commit e583d5fc52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 88 additions and 15 deletions

View file

@ -86,15 +86,18 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView):
# First try envelope_recipient field.
# According to AnymailInboundMessage it's provided not by all ESPs.
if message.envelope_recipient:
try:
token, domain = message.envelope_recipient.split("@")
except ValueError:
logger.error(
f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}"
)
return None
if domain == live_settings.INBOUND_EMAIL_DOMAIN:
return token
recipients = message.envelope_recipient.split(",")
for recipient in recipients:
# if there is more than one recipient, the first matching the expected domain will be used
try:
token, domain = recipient.strip().split("@")
except ValueError:
logger.error(
f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}"
)
continue
if domain == live_settings.INBOUND_EMAIL_DOMAIN:
return token
else:
logger.info("get_integration_token_from_request: message.envelope_recipient is not present")
"""
@ -152,7 +155,10 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView):
def get_sender_from_email_message(self, email: AnymailInboundMessage) -> str:
try:
sender = email.from_email.addr_spec
if isinstance(email.from_email, list):
sender = email.from_email[0].addr_spec
else:
sender = email.from_email.addr_spec
except AnymailInvalidAddress as e:
# wasn't able to parse email address from message, return raw value from "From" header
logger.warning(

View file

@ -1,4 +1,5 @@
import json
from textwrap import dedent
import pytest
from anymail.inbound import AnymailInboundMessage
@ -9,8 +10,19 @@ from rest_framework.test import APIClient
from apps.email.inbound import InboundEmailWebhookView
@pytest.mark.parametrize(
"recipients,expected",
[
("{token}@example.com", status.HTTP_200_OK),
("{token}@example.com, another@example.com", status.HTTP_200_OK),
("{token}@example.com, another@example.com", status.HTTP_200_OK),
("{token}@other.com, {token}@example.com", status.HTTP_400_BAD_REQUEST),
],
)
@pytest.mark.django_db
def test_amazon_ses_provider_load(settings, make_organization_and_user_with_token, make_alert_receive_channel):
def test_amazon_ses_provider_load(
settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
):
settings.INBOUND_EMAIL_ESP = "amazon_ses"
settings.INBOUND_EMAIL_DOMAIN = "example.com"
@ -19,10 +31,10 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
organization, _, token = make_organization_and_user_with_token()
_ = make_alert_receive_channel(organization, token=dummy_channel_token)
recipient = f"{dummy_channel_token}@example.com"
recipients = recipients.format(token=dummy_channel_token)
mime = f"""From: sender@example.com
Subject: Dummy email message
To: {recipient}
To: {recipients}
Content-Type: text/plain
Hello!
@ -30,7 +42,7 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
message = {
"notificationType": "Received",
"receipt": {"action": {"type": "SNS"}, "recipients": [recipient]},
"receipt": {"action": {"type": "SNS"}, "recipients": recipients.split(",")},
"content": mime,
}
@ -54,7 +66,62 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke
HTTP_X_AMZ_SNS_MESSAGE_ID=dummy_sns_message_id,
)
assert response.status_code == status.HTTP_200_OK
assert response.status_code == expected
@pytest.mark.parametrize(
"recipients,expected",
[
("{token}@example.com", status.HTTP_200_OK),
("{token}@example.com, another@example.com", status.HTTP_200_OK),
("{token}@example.com, another@example.com", status.HTTP_200_OK),
("{token}@other.com, {token}@example.com", status.HTTP_200_OK),
("{token}@other.com, {token}@another.com", status.HTTP_400_BAD_REQUEST),
],
)
@pytest.mark.django_db
def test_mailgun_provider_load(
settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected
):
settings.INBOUND_EMAIL_ESP = "mailgun"
settings.INBOUND_EMAIL_DOMAIN = "example.com"
settings.INBOUND_EMAIL_WEBHOOK_SECRET = "secret"
dummy_channel_token = "dummy-channel-token"
organization, _, token = make_organization_and_user_with_token()
_ = make_alert_receive_channel(organization, token=dummy_channel_token)
recipients = recipients.format(token=dummy_channel_token)
raw_event = {
"token": "06c96bafc3f42a66b9edd546347a2fe18dc23461fe80dc52f0",
"timestamp": "1461261330",
"signature": "dbb05e62be402448b36ffb81f6abfb888273c95617aa077b4da355b25bab3670",
"recipient": "{recipients}".format(recipients=recipients),
"sender": "envelope-from@example.org",
"body-mime": dedent(
"""\
From: sender@example.com
Subject: Dummy email message
To: {recipients}
Content-Type: text/plain
Hello!
--94eb2c05e174adb140055b6339c5--
""".format(
recipients=recipients
)
),
}
client = APIClient()
response = client.post(
reverse("integrations:inbound_email_webhook"),
data=raw_event,
HTTP_AUTHORIZATION=token,
)
assert response.status_code == expected
@pytest.mark.parametrize(