Exemple #1
0
    def test_scheduled_transient_bounce(self):
        transient_message = self._get_message('scheduled_transient_bounce')
        transient_email = '*****@*****.**'
        self.assertEqual(
            get_relevant_aws_meta(transient_message),
            [
                AwsMeta(
                    notification_type='Bounce',
                    main_type='Transient',
                    sub_type='General',
                    email='*****@*****.**',
                    reason='smtp; 554 4.4.7 Message expired: unable to deliver in 840 minutes.<421 4.4.0 Unable to lookup DNS for company.org>',
                    headers={
                        'returnPath': '*****@*****.**',
                        'from': ['*****@*****.**'],
                        'date': 'Fri, 31 Jul 2020 06:01:50 -0000',
                        'to': ['*****@*****.**'],
                        'messageId': '<redacted>',
                        'subject': 'Scheduled report from CommCare HQ'
                    },
                    timestamp=datetime.datetime(2020, 7, 31, 21, 8, 57, 723000,
                                                tzinfo=datetime.timezone.utc),
                    destination=['*****@*****.**']
                )
            ]
        )

        handle_email_sns_event(transient_message)

        bounced_email = BouncedEmail.objects.filter(email=transient_email)
        self.assertFalse(bounced_email.exists())

        transient_info = TransientBounceEmail.objects.filter(email=transient_email)
        self.assertTrue(transient_info.exists())
 def test_sns_bounce_general(self):
     sns_bounce_general = self._get_message('sns_bounce_general')
     self.assertEqual(self.manager._get_aws_info(sns_bounce_general, 333), [
         AwsMeta(notification_type='Bounce',
                 main_type='Permanent',
                 sub_type='General',
                 email='*****@*****.**',
                 reason="smtp; 550-5.1.1 The email account that you tried "
                 "to reach does not exist. Please try\n550-5.1.1 "
                 "double-checking the recipient's email address for "
                 "typos or\n550-5.1.1 unnecessary spaces. Learn more"
                 " at\n550 5.1.1  https://support.google.com/mail/?p="
                 "NoSuchUser h6si12061056qtp.98 - gsmtp",
                 headers={
                     'returnPath':
                     '*****@*****.**',
                     'from': ['*****@*****.**'],
                     'date': 'Tue, 28 Jan 2020 09:29:02 -0000',
                     'to': ['*****@*****.**'],
                     'messageId': '<redacted>',
                     'subject': 'Activate your CommCare project'
                 },
                 timestamp=datetime.datetime(
                     2020, 1, 28, 9, 29, 3, 30000, tzinfo=tzlocal()),
                 destination=['*****@*****.**'])
     ])
 def test_sns_bounce_suppressed(self):
     sns_bounce_suppressed = self._get_message('sns_bounce_suppressed')
     self.assertEqual(
         self.manager._get_aws_info(sns_bounce_suppressed, 333), [
             AwsMeta(
                 notification_type='Bounce',
                 main_type='Permanent',
                 sub_type='Suppressed',
                 email='*****@*****.**',
                 reason='Amazon SES has suppressed sending to this address '
                 'because it has a recent history of bouncing as an '
                 'invalid address. For more information about how '
                 'to remove an address from the suppression list, '
                 'see the Amazon SES Developer Guide: '
                 'http://docs.aws.amazon.com/ses/latest/'
                 'DeveloperGuide/remove-from-suppressionlist.html ',
                 headers={
                     'from': ['*****@*****.**'],
                     'date': 'Tue, 28 Jan 2020 10:40:04 -0000',
                     'to': ['*****@*****.**'],
                     'messageId': '<redacted>',
                     'subject': 'Late'
                 },
                 timestamp=datetime.datetime(
                     2020, 1, 28, 10, 40, 4, 931000, tzinfo=tzlocal()),
                 destination=['*****@*****.**'])
         ])
Exemple #4
0
def get_relevant_aws_meta(message_info):
    """
    Creates a list of AwsMeta objects from the Message portion of an AWS
    SNS Notification message.
    :param message_info: (dict) the "Message" portion of an SNS notification
    :return: (list) AwsMeta objects
    """
    aws_info = []
    mail_info = message_info.get('mail', {})
    notification_type = message_info.get(
        'notificationType') or message_info.get('eventType')
    if notification_type == NotificationType.BOUNCE:
        bounce_info = message_info['bounce']
        for recipient in bounce_info['bouncedRecipients']:
            aws_info.append(
                AwsMeta(
                    notification_type=notification_type,
                    main_type=bounce_info['bounceType'],
                    sub_type=bounce_info['bounceSubType'],
                    timestamp=parse_datetime(bounce_info['timestamp']),
                    email=recipient['emailAddress'],
                    reason=recipient.get('diagnosticCode'),
                    headers=mail_info.get('commonHeaders', {}),
                    destination=mail_info.get('destination', []),
                ))
    elif notification_type == NotificationType.COMPLAINT:
        complaint_info = message_info['complaint']
        for recipient in complaint_info['complainedRecipients']:
            aws_info.append(
                AwsMeta(
                    notification_type=notification_type,
                    main_type=message_info.get('complaintFeedbackType'),
                    sub_type=complaint_info.get('complaintSubType'),
                    timestamp=parse_datetime(complaint_info['timestamp']),
                    email=recipient['emailAddress'],
                    reason=None,
                    headers=mail_info.get('commonHeaders', {}),
                    destination=mail_info.get('destination', []),
                ))
    return aws_info
 def test_sns_bounce_complaint(self):
     sns_complaint = self._get_message('sns_complaint')
     self.assertEqual(self.manager._get_aws_info(sns_complaint, 333), [
         AwsMeta(notification_type='Complaint',
                 main_type=None,
                 sub_type='',
                 email='*****@*****.**',
                 reason=None,
                 headers={},
                 timestamp=datetime.datetime(
                     2020, 1, 8, 8, 6, 45, tzinfo=tzlocal()),
                 destination=['*****@*****.**'])
     ])
    def test_scheduled_suppressed_bounce(self):
        bounce_message = self._get_message('scheduled_suppressed_bounce')
        self.assertEqual(get_relevant_aws_meta(bounce_message), [
            AwsMeta(
                notification_type='Bounce',
                main_type='Permanent',
                sub_type='Suppressed',
                email='*****@*****.**',
                reason=
                'Amazon SES has suppressed sending to this address because '
                'it has a recent history of bouncing as an invalid address. '
                'For more information about how to remove an address from the '
                'suppression list, see the Amazon SES Developer Guide: '
                'http://docs.aws.amazon.com/ses/latest/DeveloperGuide/remove-from-suppressionlist.html ',
                headers={
                    'returnPath': '*****@*****.**',
                    'from': ['*****@*****.**'],
                    'date': 'Tue, 28 Jul 2020 00:28:28 -0000',
                    'to': ['*****@*****.**'],
                    'cc': ['*****@*****.**'],
                    'messageId': '<redacted@server-name>',
                    'subject': 'Invitation from John Doe to join CommCareHQ'
                },
                timestamp=datetime.datetime(2020,
                                            7,
                                            28,
                                            0,
                                            28,
                                            28,
                                            622000,
                                            tzinfo=datetime.timezone.utc),
                destination=[
                    '*****@*****.**', '*****@*****.**'
                ])
        ])
        handle_email_sns_event(bounce_message)

        bounced_email = BouncedEmail.objects.filter(
            email='*****@*****.**')
        self.assertTrue(bounced_email.exists())

        permanent_meta = PermanentBounceMeta.objects.filter(
            bounced_email=bounced_email.first())
        self.assertTrue(permanent_meta.exists())
        self.assertEqual(permanent_meta.first().sub_type,
                         BounceSubType.SUPPRESSED)
    def test_scheduled_general_bounce(self):
        bounce_message = self._get_message('scheduled_general_bounce')
        self.assertEqual(get_relevant_aws_meta(bounce_message), [
            AwsMeta(
                notification_type='Bounce',
                main_type='Permanent',
                sub_type='General',
                email='*****@*****.**',
                reason=
                'smtp; 550 5.4.1 Recipient address rejected: Access denied. AS(redacted) [redacted.outlook.com]',
                headers={
                    'returnPath': '*****@*****.**',
                    'from': ['*****@*****.**'],
                    'date': 'Fri, 31 Jul 2020 20:09:55 -0000',
                    'to': ['*****@*****.**'],
                    'cc': ['*****@*****.**'],
                    'messageId': '<redacted@server-name>',
                    'subject': 'Invitation from John Doe to join CommCareHQ'
                },
                timestamp=datetime.datetime(2020,
                                            7,
                                            31,
                                            20,
                                            9,
                                            56,
                                            637000,
                                            tzinfo=datetime.timezone.utc),
                destination=[
                    '*****@*****.**',
                    '*****@*****.**'
                ])
        ])
        handle_email_sns_event(bounce_message)

        bounced_email = BouncedEmail.objects.filter(
            email='*****@*****.**')
        self.assertTrue(bounced_email.exists())

        permanent_meta = PermanentBounceMeta.objects.filter(
            bounced_email=bounced_email.first())
        self.assertTrue(permanent_meta.exists())
        self.assertEqual(permanent_meta.first().sub_type,
                         BounceSubType.GENERAL)
 def test_sns_bounce_transient(self):
     sns_bounce_transient = self._get_message('sns_bounce_transient')
     self.assertEqual(self.manager._get_aws_info(
         sns_bounce_transient, 333), [
             AwsMeta(
                 notification_type='Bounce',
                 main_type='Transient',
                 sub_type='General',
                 email='*****@*****.**',
                 reason=None,
                 headers={
                     'returnPath':
                     '*****@*****.**',
                     'from': ['*****@*****.**'],
                     'date': 'Tue, 28 Jan 2020 13:00:27 -0000',
                     'to': ['*****@*****.**'],
                     'messageId': '<redacted>',
                     'subject': 'Scheduled report from CommCare HQ'
                 },
                 timestamp=datetime.datetime(
                     2020, 1, 28, 13, 0, 35, tzinfo=tzlocal()),
                 destination=['*****@*****.**'])
         ])
    def _get_aws_info(self, message, uid):
        """
        This gets the most important details from AWS SNS notifications for
        Complaints and Bounces.

        Relevant AWS Dcoumentation:
        https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-examples.html

        :param message: raw message
        :param uid: SMTP uid of message
        :return: list of AwsMeta objects
        """
        aws_info = []
        base_info = self._get_message_json(message)

        if base_info.get('notificationType'):
            message_info = base_info
        else:
            message_info = base_info.get('Message')
            if isinstance(message_info, str):
                message_info = json.loads(message_info)

        if not message_info:
            return aws_info

        mail_info = message_info.get('mail', {})
        if message_info['notificationType'] == NotificationType.BOUNCE:
            bounce_info = message_info['bounce']
            for recipient in bounce_info['bouncedRecipients']:
                aws_info.append(
                    AwsMeta(
                        notification_type=message_info['notificationType'],
                        main_type=bounce_info['bounceType'],
                        sub_type=bounce_info['bounceSubType'],
                        timestamp=parse_datetime(bounce_info['timestamp']),
                        email=recipient['emailAddress'],
                        reason=recipient.get('diagnosticCode'),
                        headers=mail_info.get('commonHeaders', {}),
                        destination=mail_info.get('destination', []),
                    ))
        elif message_info['notificationType'] == NotificationType.COMPLAINT:
            complaint_info = message_info['complaint']
            for recipient in complaint_info['complainedRecipients']:
                aws_info.append(
                    AwsMeta(
                        notification_type=message_info['notificationType'],
                        main_type=message_info.get('complaintFeedbackType'),
                        sub_type=complaint_info.get('complaintSubType'),
                        timestamp=parse_datetime(complaint_info['timestamp']),
                        email=recipient['emailAddress'],
                        reason=None,
                        headers=mail_info.get('commonHeaders', {}),
                        destination=mail_info.get('destination', []),
                    ))
        else:
            metrics_counter(
                'commcare.bounced_email_manager.unknown_notification_type')
            self._label_problem_email(
                uid, extra_labels=['UnknownAWSNotificationType'])
            _bounced_email_soft_assert(
                False, f'[{settings.SERVER_ENVIRONMENT}] '
                f'Unknown AWS Notification Type sent to Inbox. ')

        return aws_info
Exemple #10
0
    def test_scheduled_complaint(self):
        complaint_message = self._get_message('scheduled_complaint')
        self.assertEqual(
            get_relevant_aws_meta(complaint_message),
            [
                AwsMeta(
                    notification_type='Complaint',
                    main_type=None,
                    sub_type=None,
                    email='*****@*****.**',
                    reason=None,
                    headers={
                        'returnPath': '*****@*****.**',
                        'from': ['*****@*****.**'],
                        'date': 'Thu, 16 Jul 2020 03:02:22 -0000',
                        'to': ['*****@*****.**'],
                        'cc': ['*****@*****.**'],
                        'messageId': '<redacted@server-name>',
                        'subject': 'Invitation from Alice Doe to join CommCareHQ'
                    },
                    timestamp=datetime.datetime(2020, 7, 16, 3, 16, 55, 129000,
                                                tzinfo=datetime.timezone.utc),
                    destination=[
                        '*****@*****.**',
                        '*****@*****.**'
                    ]
                ),
                AwsMeta(
                    notification_type='Complaint',
                    main_type=None,
                    sub_type=None,
                    email='*****@*****.**',
                    reason=None,
                    headers={
                        'returnPath': '*****@*****.**',
                        'from': ['*****@*****.**'],
                        'date': 'Thu, 16 Jul 2020 03:02:22 -0000',
                        'to': ['*****@*****.**'],
                        'cc': ['*****@*****.**'],
                        'messageId': '<redacted@server-name>',
                        'subject': 'Invitation from Alice Doe to join CommCareHQ'
                    },
                    timestamp=datetime.datetime(2020, 7, 16, 3, 16, 55, 129000,
                                                tzinfo=datetime.timezone.utc),
                    destination=[
                        '*****@*****.**',
                        '*****@*****.**'
                    ]
                ),
            ]
        )
        handle_email_sns_event(complaint_message)

        first_bounced_email = BouncedEmail.objects.filter(email='*****@*****.**')
        second_bounced_email = BouncedEmail.objects.filter(email='*****@*****.**')
        self.assertTrue(first_bounced_email.exists())
        self.assertTrue(second_bounced_email.exists())

        self.assertTrue(
            ComplaintBounceMeta.objects.filter(
                bounced_email=first_bounced_email.first()
            ).exists()
        )
        self.assertTrue(
            ComplaintBounceMeta.objects.filter(
                bounced_email=second_bounced_email.first()
            ).exists()
        )