From 797a10afac80544e2d69bcb7d624909436f2b12a Mon Sep 17 00:00:00 2001 From: Ran Isenberg <60175085+ran-isenberg@users.noreply.github.com> Date: Thu, 28 Apr 2022 19:30:57 +0300 Subject: [PATCH] fix(parser): Add missing fields for SESEvent (#1027) Co-authored-by: Ran Isenberg --- .../utilities/parser/models/__init__.py | 8 ++ .../utilities/parser/models/ses.py | 34 +++++- tests/events/sesEventS3.json | 114 ++++++++++++++++++ tests/functional/parser/test_ses.py | 58 ++++++++- 4 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 tests/events/sesEventS3.json diff --git a/aws_lambda_powertools/utilities/parser/models/__init__.py b/aws_lambda_powertools/utilities/parser/models/__init__.py index e3fb50a2d5d..34c8e6ce6a1 100644 --- a/aws_lambda_powertools/utilities/parser/models/__init__.py +++ b/aws_lambda_powertools/utilities/parser/models/__init__.py @@ -37,7 +37,11 @@ SesModel, SesReceipt, SesReceiptAction, + SesReceiptActionBase, + SesReceiptBounceAction, + SesReceiptS3Action, SesReceiptVerdict, + SesReceiptWorkmailAction, SesRecordModel, ) from .sns import SnsModel, SnsNotificationModel, SnsRecordModel @@ -84,6 +88,10 @@ "SesMailHeaders", "SesReceipt", "SesReceiptAction", + "SesReceiptActionBase", + "SesReceiptBounceAction", + "SesReceiptWorkmailAction", + "SesReceiptS3Action", "SesReceiptVerdict", "SnsModel", "SnsNotificationModel", diff --git a/aws_lambda_powertools/utilities/parser/models/ses.py b/aws_lambda_powertools/utilities/parser/models/ses.py index 70fd2e83978..7cd655ea28c 100644 --- a/aws_lambda_powertools/utilities/parser/models/ses.py +++ b/aws_lambda_powertools/utilities/parser/models/ses.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import List, Optional +from typing import List, Optional, Union from pydantic import BaseModel, Field from pydantic.networks import EmailStr @@ -12,12 +12,38 @@ class SesReceiptVerdict(BaseModel): status: Literal["PASS", "FAIL", "GRAY", "PROCESSING_FAILED"] -class SesReceiptAction(BaseModel): +class SesReceiptActionBase(BaseModel): + topicArn: Optional[str] + + +class SesReceiptAction(SesReceiptActionBase): type: Literal["Lambda"] # noqa A003,VNE003 invocationType: Literal["Event"] functionArn: str +class SesReceiptS3Action(SesReceiptActionBase): + type: Literal["S3"] # noqa A003,VNE003 + topicArn: str + bucketName: str + objectKey: str + + +class SesReceiptBounceAction(SesReceiptActionBase): + type: Literal["Bounce"] # noqa A003,VNE003 + topicArn: str + smtpReplyCode: str + message: str + sender: str + statusCode: str + + +class SesReceiptWorkmailAction(SesReceiptActionBase): + type: Literal["WorkMail"] # noqa A003,VNE003 + topicArn: str + organizationArn: str + + class SesReceipt(BaseModel): timestamp: datetime processingTimeMillis: PositiveInt @@ -25,8 +51,10 @@ class SesReceipt(BaseModel): spamVerdict: SesReceiptVerdict virusVerdict: SesReceiptVerdict spfVerdict: SesReceiptVerdict + dkimVerdict: SesReceiptVerdict dmarcVerdict: SesReceiptVerdict - action: SesReceiptAction + dmarcPolicy: Optional[Literal["quarantine", "reject", "none"]] + action: Union[SesReceiptAction, SesReceiptS3Action, SesReceiptBounceAction, SesReceiptWorkmailAction] class SesMailHeaders(BaseModel): diff --git a/tests/events/sesEventS3.json b/tests/events/sesEventS3.json new file mode 100644 index 00000000000..dbea2d42ce1 --- /dev/null +++ b/tests/events/sesEventS3.json @@ -0,0 +1,114 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "receipt": { + "timestamp": "2015-09-11T20:32:33.936Z", + "processingTimeMillis": 406, + "recipients": [ + "recipient@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "action": { + "type": "S3", + "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic", + "bucketName": "my-S3-bucket", + "objectKey": "email" + } + }, + "mail": { + "timestamp": "2015-09-11T20:32:33.936Z", + "source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "messageId": "d6iitobk75ur44p8kdnnp7g2n800", + "destination": [ + "recipient@example.com" + ], + "headersTruncated": false, + "headers": [ + { + "name": "Return-Path", + "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" + }, + { + "name": "Received", + "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" + }, + { + "name": "From", + "value": "sender@example.com" + }, + { + "name": "To", + "value": "recipient@example.com" + }, + { + "name": "Subject", + "value": "Example subject" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "7bit" + }, + { + "name": "Date", + "value": "Fri, 11 Sep 2015 20:32:32 +0000" + }, + { + "name": "Message-ID", + "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" + }, + { + "name": "X-SES-Outgoing", + "value": "2015.09.11-54.240.9.183" + }, + { + "name": "Feedback-ID", + "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" + } + ], + "commonHeaders": { + "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "from": [ + "sender@example.com" + ], + "date": "Fri, 11 Sep 2015 20:32:32 +0000", + "to": [ + "recipient@example.com" + ], + "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", + "subject": "Example subject" + } + } + }, + "eventSource": "aws:ses" + } + ] +} diff --git a/tests/functional/parser/test_ses.py b/tests/functional/parser/test_ses.py index d434e2350f8..34a44253514 100644 --- a/tests/functional/parser/test_ses.py +++ b/tests/functional/parser/test_ses.py @@ -1,11 +1,22 @@ from aws_lambda_powertools.utilities.parser import event_parser -from aws_lambda_powertools.utilities.parser.models import SesModel, SesRecordModel +from aws_lambda_powertools.utilities.parser.models import ( + SesModel, + SesReceiptBounceAction, + SesReceiptWorkmailAction, + SesRecordModel, +) from aws_lambda_powertools.utilities.typing import LambdaContext from tests.functional.utils import load_event @event_parser(model=SesModel) -def handle_ses(event: SesModel, _: LambdaContext): +def handle_ses(event: SesModel, _: LambdaContext) -> SesModel: + return event + + +def test_ses_trigger_lambda_event(): + event_dict = load_event("sesEvent.json") + event = handle_ses(event_dict, LambdaContext()) expected_address = "johndoe@example.com" records = event.Records record: SesRecordModel = records[0] @@ -29,6 +40,10 @@ def handle_ses(event: SesModel, _: LambdaContext): assert common_headers.to == [expected_address] assert common_headers.messageId == "<0123456789example.com>" assert common_headers.subject == "Test Subject" + assert common_headers.cc is None + assert common_headers.bcc is None + assert common_headers.sender is None + assert common_headers.reply_to is None receipt = record.ses.receipt convert_time = int(round(receipt.timestamp.timestamp() * 1000)) assert convert_time == 0 @@ -38,12 +53,45 @@ def handle_ses(event: SesModel, _: LambdaContext): assert receipt.virusVerdict.status == "PASS" assert receipt.spfVerdict.status == "PASS" assert receipt.dmarcVerdict.status == "PASS" + assert receipt.dmarcVerdict.status == "PASS" + assert receipt.dmarcPolicy is None action = receipt.action assert action.type == "Lambda" assert action.functionArn == "arn:aws:lambda:us-west-2:012345678912:function:Example" assert action.invocationType == "Event" + assert action.topicArn is None -def test_ses_trigger_event(): - event_dict = load_event("sesEvent.json") - handle_ses(event_dict, LambdaContext()) +def test_ses_trigger_event_s3(): + event_dict = load_event("sesEventS3.json") + event = handle_ses(event_dict, LambdaContext()) + records = list(event.Records) + record = records[0] + receipt = record.ses.receipt + assert receipt.dmarcPolicy == "reject" + action = record.ses.receipt.action + assert action.type == "S3" + assert action.topicArn == "arn:aws:sns:us-east-1:012345678912:example-topic" + assert action.bucketName == "my-S3-bucket" + assert action.objectKey == "email" + + +def test_ses_trigger_event_bounce(): + event_dict = { + "type": "Bounce", + "topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic", + "smtpReplyCode": "5.1.1", + "message": "message", + "sender": "sender", + "statusCode": "550", + } + SesReceiptBounceAction(**event_dict) + + +def test_ses_trigger_event_work_mail(): + event_dict = { + "type": "WorkMail", + "topicArn": "arn:aws:sns:us-east-1:123456789012:topic:my-topic", + "organizationArn": "arn", + } + SesReceiptWorkmailAction(**event_dict)