Esempio n. 1
0
def do_subscribe(topic_arn, endpoint, protocol, subscription_arn, attributes, filter_policy=None):
    # An endpoint may only be subscribed to a topic once. Subsequent
    # subscribe calls do nothing (subscribe is idempotent).
    for existing_topic_subscription in SNS_SUBSCRIPTIONS.get(topic_arn, []):
        if existing_topic_subscription.get('Endpoint') == endpoint:
            return

    subscription = {
        # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html
        'TopicArn': topic_arn,
        'Endpoint': endpoint,
        'Protocol': protocol,
        'SubscriptionArn': subscription_arn,
        'FilterPolicy': filter_policy
    }
    subscription.update(attributes)
    SNS_SUBSCRIPTIONS[topic_arn].append(subscription)

    # Send out confirmation message for HTTP(S), fix for https://github.com/localstack/localstack/issues/881
    if protocol in ['http', 'https']:
        token = short_uid()
        external_url = external_service_url('sns')
        confirmation = {
            'Type': ['SubscriptionConfirmation'],
            'Token': [token],
            'Message': [('You have chosen to subscribe to the topic %s.\n' % topic_arn) +
                'To confirm the subscription, visit the SubscribeURL included in this message.'],
            'SubscribeURL': ['%s/?Action=ConfirmSubscription&TopicArn=%s&Token=%s' % (external_url, topic_arn, token)]
        }
        publish_message(topic_arn, confirmation, subscription_arn)
Esempio n. 2
0
def do_subscribe(topic_arn,
                 endpoint,
                 protocol,
                 subscription_arn,
                 attributes,
                 filter_policy=None):
    sns_backend = SNSBackend.get()
    topic_subs = sns_backend.sns_subscriptions[
        topic_arn] = sns_backend.sns_subscriptions.get(topic_arn) or []
    # An endpoint may only be subscribed to a topic once. Subsequent
    # subscribe calls do nothing (subscribe is idempotent).
    for existing_topic_subscription in topic_subs:
        if existing_topic_subscription.get('Endpoint') == endpoint:
            return

    subscription = {
        # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html
        'TopicArn': topic_arn,
        'Endpoint': endpoint,
        'Protocol': protocol,
        'SubscriptionArn': subscription_arn,
        'FilterPolicy': filter_policy
    }
    subscription.update(attributes)
    topic_subs.append(subscription)

    if subscription_arn not in sns_backend.subscription_status:
        sns_backend.subscription_status[subscription_arn] = {}

    sns_backend.subscription_status[subscription_arn].update({
        'TopicArn':
        topic_arn,
        'Token':
        short_uid(),
        'Status':
        'Not Subscribed'
    })
    # Send out confirmation message for HTTP(S), fix for https://github.com/localstack/localstack/issues/881
    if protocol in ['http', 'https']:
        token = short_uid()
        external_url = external_service_url('sns')
        subscription[
            'UnsubscribeURL'] = '%s/?Action=Unsubscribe&SubscriptionArn=%s' % (
                external_url, subscription_arn)
        confirmation = {
            'Type': ['SubscriptionConfirmation'],
            'Token': [token],
            'Message':
            [('You have chosen to subscribe to the topic %s.\n' % topic_arn) +
             'To confirm the subscription, visit the SubscribeURL included in this message.'
             ],
            'SubscribeURL': [
                '%s/?Action=ConfirmSubscription&TopicArn=%s&Token=%s' %
                (external_url, topic_arn, token)
            ]
        }
        publish_message(topic_arn,
                        confirmation, {},
                        subscription_arn,
                        skip_checks=True)
Esempio n. 3
0
 def url(self, context: RequestContext) -> str:
     """Return queue URL using either SQS_PORT_EXTERNAL (if configured), or based on the 'Host' request header"""
     host_url = context.request.host_url
     if config.SQS_PORT_EXTERNAL:
         host_url = external_service_url("sqs")
     return "{host}/{account_id}/{name}".format(
         host=host_url.rstrip("/"),
         account_id=self.key.account_id,
         name=self.key.name,
     )
Esempio n. 4
0
def create_sns_message_body(subscriber, req_data):
    message = req_data['Message'][0]
    subject = req_data.get('Subject', [None])[0]
    protocol = subscriber['Protocol']

    if six.PY2 and type(message).__name__ == 'unicode':
        # fix non-ascii unicode characters under Python 2
        message = message.encode('raw-unicode-escape')

    if is_raw_message_delivery(subscriber):
        return message

    if req_data.get('MessageStructure') == ['json']:
        message = json.loads(message)
        try:
            message = message.get(protocol, message['default'])
        except KeyError:
            raise Exception("Unable to find 'default' key in message payload")

    token = short_uid()
    external_url = external_service_url('sns')
    topic_arn = subscriber['TopicArn']

    data = {
        'Type':
        req_data.get('Type', ['Notification'])[0],
        'MessageId':
        str(uuid.uuid4()),
        'TopicArn':
        topic_arn,
        'Message':
        message,
        'Timestamp':
        timestamp_millis(),
        'SignatureVersion':
        '1',
        # TODO Add a more sophisticated solution with an actual signature
        # Hardcoded
        'Signature':
        'EXAMPLEpH+..',
        'SigningCertURL':
        'https://sns.us-east-1.amazonaws.com/SimpleNotificationService-0000000000000000000000.pem',
        'UnsubscribeURL':
        '%s/?Action=Unsubscribe&TopicArn=%s&Token=%s' %
        (external_url, topic_arn, token)
    }

    if subject is not None:
        data['Subject'] = subject

    attributes = get_message_attributes(req_data)
    if attributes:
        data['MessageAttributes'] = attributes

    return json.dumps(data)
Esempio n. 5
0
        def received():
            self.assertEqual(records[0][0]['Type'], 'SubscriptionConfirmation')
            self.assertEqual(records[0][1]['x-amz-sns-message-type'], 'SubscriptionConfirmation')

            token = records[0][0]['Token']
            subscribe_url = records[0][0]['SubscribeURL']

            self.assertEqual(subscribe_url, '%s/?Action=ConfirmSubscription&TopicArn=%s&Token=%s' % (
                external_service_url('sns'), self.topic_arn, token))

            self.assertIn('Signature', records[0][0])
            self.assertIn('SigningCertURL', records[0][0])
Esempio n. 6
0
def do_subscribe(topic_arn, endpoint, protocol, subscription_arn, attributes, filter_policy=None):
    sns_backend = SNSBackend.get()
    topic_subs = sns_backend.sns_subscriptions[topic_arn] = (
        sns_backend.sns_subscriptions.get(topic_arn) or []
    )
    # An endpoint may only be subscribed to a topic once. Subsequent
    # subscribe calls do nothing (subscribe is idempotent).
    for existing_topic_subscription in topic_subs:
        if existing_topic_subscription.get("Endpoint") == endpoint:
            return

    subscription = {
        # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html
        "TopicArn": topic_arn,
        "Endpoint": endpoint,
        "Protocol": protocol,
        "SubscriptionArn": subscription_arn,
        "FilterPolicy": filter_policy,
    }
    subscription.update(attributes)
    topic_subs.append(subscription)

    if subscription_arn not in sns_backend.subscription_status:
        sns_backend.subscription_status[subscription_arn] = {}

    sns_backend.subscription_status[subscription_arn].update(
        {"TopicArn": topic_arn, "Token": short_uid(), "Status": "Not Subscribed"}
    )
    # Send out confirmation message for HTTP(S), fix for https://github.com/localstack/localstack/issues/881
    if protocol in ["http", "https"]:
        token = short_uid()
        external_url = external_service_url("sns")
        subscription["UnsubscribeURL"] = "%s/?Action=Unsubscribe&SubscriptionArn=%s" % (
            external_url,
            subscription_arn,
        )
        confirmation = {
            "Type": ["SubscriptionConfirmation"],
            "Token": [token],
            "Message": [
                ("You have chosen to subscribe to the topic %s.\n" % topic_arn)
                + "To confirm the subscription, visit the SubscribeURL included in this message."
            ],
            "SubscribeURL": [
                "%s/?Action=ConfirmSubscription&TopicArn=%s&Token=%s"
                % (external_url, topic_arn, token)
            ],
        }
        publish_message(topic_arn, confirmation, {}, subscription_arn, skip_checks=True)
Esempio n. 7
0
        def received():
            self.assertEqual(records[0][0]["Type"], "SubscriptionConfirmation")
            self.assertEqual(records[0][1]["x-amz-sns-message-type"], "SubscriptionConfirmation")

            token = records[0][0]["Token"]
            subscribe_url = records[0][0]["SubscribeURL"]

            self.assertEqual(
                subscribe_url,
                "%s/?Action=ConfirmSubscription&TopicArn=%s&Token=%s"
                % (external_service_url("sns"), self.topic_arn, token),
            )

            self.assertIn("Signature", records[0][0])
            self.assertIn("SigningCertURL", records[0][0])
Esempio n. 8
0
    def should_be_kept(current_subscription, target_subscription_arn):
        if current_subscription["SubscriptionArn"] != target_subscription_arn:
            return True

        if current_subscription["Protocol"] in ["http", "https"]:
            external_url = external_service_url("sns")
            token = short_uid()
            message_id = long_uid()
            subscription_url = "%s/?Action=ConfirmSubscription&SubscriptionArn=%s&Token=%s" % (
                external_url,
                target_subscription_arn,
                token,
            )
            message = {
                "Type": ["UnsubscribeConfirmation"],
                "MessageId": [message_id],
                "Token": [token],
                "TopicArn": [current_subscription["TopicArn"]],
                "Message": [
                    "You have chosen to deactivate subscription %s.\nTo cancel this operation and restore the subscription, visit the SubscribeURL included in this message."
                    % target_subscription_arn
                ],
                "SubscribeURL": [subscription_url],
                "Timestamp": [datetime.datetime.utcnow().timestamp()],
            }

            headers = {
                "x-amz-sns-message-type": "UnsubscribeConfirmation",
                "x-amz-sns-message-id": message_id,
                "x-amz-sns-topic-arn": current_subscription["TopicArn"],
                "x-amz-sns-subscription-arn": target_subscription_arn,
            }
            publish_message(
                current_subscription["TopicArn"],
                message,
                headers,
                subscription_arn,
                skip_checks=True,
            )

        return False
Esempio n. 9
0
def publish_message(topic_arn, req_data, subscription_arn=None):
    message = req_data['Message'][0]
    sqs_client = aws_stack.connect_to_service('sqs')

    LOG.debug('Publishing message to TopicArn: %s | Message:  %s' % (topic_arn, message))

    subscriptions = SNS_SUBSCRIPTIONS.get(topic_arn, [])
    for subscriber in list(subscriptions):
        if subscription_arn not in [None, subscriber['SubscriptionArn']]:
            continue
        filter_policy = json.loads(subscriber.get('FilterPolicy') or '{}')
        message_attributes = get_message_attributes(req_data)
        if not check_filter_policy(filter_policy, message_attributes):
            continue

        if subscriber['Protocol'] == 'sqs':
            queue_url = None
            try:
                endpoint = subscriber['Endpoint']
                if 'sqs_queue_url' in subscriber:
                    queue_url = subscriber.get('sqs_queue_url')
                elif '://' in endpoint:
                    queue_url = endpoint
                else:
                    queue_name = endpoint.split(':')[5]
                    queue_url = aws_stack.get_sqs_queue_url(queue_name)
                    subscriber['sqs_queue_url'] = queue_url

                sqs_client.send_message(
                    QueueUrl=queue_url,
                    MessageBody=create_sns_message_body(subscriber, req_data),
                    MessageAttributes=create_sqs_message_attributes(subscriber, message_attributes)
                )
            except Exception as exc:
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'], req_data, str(exc))
                if 'NonExistentQueue' in str(exc):
                    LOG.info('Removing non-existent queue "%s" subscribed to topic "%s"' % (queue_url, topic_arn))
                    subscriptions.remove(subscriber)

        elif subscriber['Protocol'] == 'lambda':
            try:
                external_url = external_service_url('sns')
                unsubscribe_url = '%s/?Action=Unsubscribe&SubscriptionArn=%s' % (external_url,
                                    subscriber['SubscriptionArn'])
                response = lambda_api.process_sns_notification(
                    subscriber['Endpoint'],
                    topic_arn,
                    subscriber['SubscriptionArn'],
                    message,
                    message_attributes,
                    unsubscribe_url,
                    subject=req_data.get('Subject', [None])[0]
                )
                if isinstance(response, FlaskResponse):
                    response.raise_for_status()
            except Exception as exc:
                LOG.warning('Unable to run Lambda function on SNS message: %s %s' % (exc, traceback.format_exc()))
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'], req_data, str(exc))

        elif subscriber['Protocol'] in ['http', 'https']:
            msg_type = (req_data.get('Type') or ['Notification'])[0]
            try:
                message_body = create_sns_message_body(subscriber, req_data)
            except Exception:
                continue
            try:
                response = requests.post(
                    subscriber['Endpoint'],
                    headers={
                        'Content-Type': 'text/plain',
                        # AWS headers according to
                        # https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-header
                        'x-amz-sns-message-type': msg_type,
                        'x-amz-sns-topic-arn': subscriber['TopicArn'],
                        'x-amz-sns-subscription-arn': subscriber['SubscriptionArn'],
                        'User-Agent': 'Amazon Simple Notification Service Agent'
                    },
                    data=message_body,
                    verify=False
                )
                response.raise_for_status()
            except Exception as exc:
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'], req_data, str(exc))
        else:
            LOG.warning('Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol'])
Esempio n. 10
0
async def message_to_subscriber(
    message_id,
    message,
    topic_arn,
    req_data,
    headers,
    subscription_arn,
    skip_checks,
    sns_backend,
    subscriber,
    subscriptions,
    message_attributes,
):

    if subscription_arn not in [None, subscriber["SubscriptionArn"]]:
        return

    filter_policy = json.loads(subscriber.get("FilterPolicy") or "{}")
    if not message_attributes:
        message_attributes = get_message_attributes(req_data)
    if not skip_checks and not check_filter_policy(filter_policy,
                                                   message_attributes):
        LOG.info("SNS filter policy %s does not match attributes %s",
                 filter_policy, message_attributes)
        return
    if subscriber["Protocol"] == "sms":
        event = {
            "topic_arn": topic_arn,
            "endpoint": subscriber["Endpoint"],
            "message_content": req_data["Message"][0],
        }
        sns_backend.sms_messages.append(event)
        LOG.info(
            "Delivering SMS message to %s: %s",
            subscriber["Endpoint"],
            req_data["Message"][0],
        )

        # MOCK DATA
        delivery = {
            "phoneCarrier": "Mock Carrier",
            "mnc": 270,
            "priceInUSD": 0.00645,
            "smsType": "Transactional",
            "mcc": 310,
            "providerResponse": "Message has been accepted by phone carrier",
            "dwellTimeMsUntilDeviceAck": 200,
        }
        store_delivery_log(subscriber, True, message, message_id, delivery)
        return

    elif subscriber["Protocol"] == "sqs":
        queue_url = None

        try:
            endpoint = subscriber["Endpoint"]

            if "sqs_queue_url" in subscriber:
                queue_url = subscriber.get("sqs_queue_url")
            elif "://" in endpoint:
                queue_url = endpoint
            else:
                queue_name = endpoint.split(":")[5]
                queue_url = aws_stack.get_sqs_queue_url(queue_name)
                subscriber["sqs_queue_url"] = queue_url

            message_group_id = (req_data.get("MessageGroupId")
                                if req_data.get("MessageGroupId") else "")

            if isinstance(message_group_id, list):
                message_group_id = message_group_id[0]

            message_deduplication_id = (
                req_data.get("MessageDeduplicationId")[0]
                if req_data.get("MessageDeduplicationId") else "")

            sqs_client = aws_stack.connect_to_service("sqs")

            kwargs = {}
            if message_group_id:
                kwargs["MessageGroupId"] = message_group_id
            if message_deduplication_id:
                kwargs["MessageDeduplicationId"] = message_deduplication_id

            sqs_client.send_message(
                QueueUrl=queue_url,
                MessageBody=create_sns_message_body(subscriber, req_data,
                                                    message_id),
                MessageAttributes=create_sqs_message_attributes(
                    subscriber, message_attributes),
                MessageSystemAttributes=create_sqs_system_attributes(headers),
                **kwargs,
            )
            store_delivery_log(subscriber, True, message, message_id)
        except Exception as exc:
            LOG.info("Unable to forward SNS message to SQS: %s %s", exc,
                     traceback.format_exc())
            store_delivery_log(subscriber, False, message, message_id)
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
            if "NonExistentQueue" in str(exc):
                LOG.info(
                    'Removing non-existent queue "%s" subscribed to topic "%s"',
                    queue_url,
                    topic_arn,
                )
                subscriptions.remove(subscriber)
        return

    elif subscriber["Protocol"] == "lambda":
        try:
            external_url = external_service_url("sns")
            unsubscribe_url = "%s/?Action=Unsubscribe&SubscriptionArn=%s" % (
                external_url,
                subscriber["SubscriptionArn"],
            )
            response = lambda_api.process_sns_notification(
                subscriber["Endpoint"],
                topic_arn,
                subscriber["SubscriptionArn"],
                message,
                message_id,
                message_attributes,
                unsubscribe_url,
                subject=req_data.get("Subject", [None])[0],
            )

            if response is not None:
                delivery = {
                    "statusCode": response.status_code,
                    "providerResponse": response.get_data(),
                }
                store_delivery_log(subscriber, True, message, message_id,
                                   delivery)

            if isinstance(response, Response):
                response.raise_for_status()
            elif isinstance(response, FlaskResponse):
                if response.status_code >= 400:
                    raise Exception("Error response (code %s): %s" %
                                    (response.status_code, response.data))
        except Exception as exc:
            LOG.info("Unable to run Lambda function on SNS message: %s %s",
                     exc, traceback.format_exc())
            store_delivery_log(subscriber, False, message, message_id)
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] in ["http", "https"]:
        msg_type = (req_data.get("Type") or ["Notification"])[0]
        try:
            message_body = create_sns_message_body(subscriber, req_data,
                                                   message_id)
        except Exception:
            return
        try:
            response = requests.post(
                subscriber["Endpoint"],
                headers={
                    "Content-Type": "text/plain",
                    # AWS headers according to
                    # https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-header
                    "x-amz-sns-message-type": msg_type,
                    "x-amz-sns-topic-arn": subscriber["TopicArn"],
                    "x-amz-sns-subscription-arn":
                    subscriber["SubscriptionArn"],
                    "User-Agent": "Amazon Simple Notification Service Agent",
                },
                data=message_body,
                verify=False,
            )

            delivery = {
                "statusCode": response.status_code,
                "providerResponse": response.content.decode("utf-8"),
            }
            store_delivery_log(subscriber, True, message, message_id, delivery)

            response.raise_for_status()
        except Exception as exc:
            LOG.info(
                "Received error on sending SNS message, putting to DLQ (if configured): %s",
                exc)
            store_delivery_log(subscriber, False, message, message_id)
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] == "application":
        try:
            sns_client = aws_stack.connect_to_service("sns")
            sns_client.publish(TargetArn=subscriber["Endpoint"],
                               Message=message)
            store_delivery_log(subscriber, True, message, message_id)
        except Exception as exc:
            LOG.warning(
                "Unable to forward SNS message to SNS platform app: %s %s",
                exc,
                traceback.format_exc(),
            )
            store_delivery_log(subscriber, False, message, message_id)
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] in ["email", "email-json"]:
        ses_client = aws_stack.connect_to_service("ses")
        if subscriber.get("Endpoint"):
            ses_client.verify_email_address(
                EmailAddress=subscriber.get("Endpoint"))
            ses_client.verify_email_address(
                EmailAddress="*****@*****.**")

            ses_client.send_email(
                Source="*****@*****.**",
                Message={
                    "Body": {
                        "Text": {
                            "Data":
                            create_sns_message_body(subscriber=subscriber,
                                                    req_data=req_data,
                                                    message_id=message_id) if
                            subscriber["Protocol"] == "email-json" else message
                        }
                    },
                    "Subject": {
                        "Data": "SNS-Subscriber-Endpoint"
                    },
                },
                Destination={"ToAddresses": [subscriber.get("Endpoint")]},
            )
            store_delivery_log(subscriber, True, message, message_id)
    else:
        LOG.warning('Unexpected protocol "%s" for SNS subscription',
                    subscriber["Protocol"])
Esempio n. 11
0
def message_to_subscribers(message_id,
                           message,
                           topic_arn,
                           req_data,
                           subscription_arn=None,
                           skip_checks=False):
    subscriptions = SNS_SUBSCRIPTIONS.get(topic_arn, [])
    for subscriber in list(subscriptions):
        if subscription_arn not in [None, subscriber['SubscriptionArn']]:
            continue

        filter_policy = json.loads(subscriber.get('FilterPolicy') or '{}')
        message_attributes = get_message_attributes(req_data)
        if not skip_checks and not check_filter_policy(filter_policy,
                                                       message_attributes):
            LOG.info('SNS filter policy %s does not match attributes %s' %
                     (filter_policy, message_attributes))
            continue

        if subscriber['Protocol'] == 'sms':
            event = {
                'topic_arn': topic_arn,
                'endpoint': subscriber['Endpoint'],
                'message_content': req_data['Message'][0]
            }
            SMS_MESSAGES.append(event)
            LOG.info('Delivering SMS message to %s: %s',
                     subscriber['Endpoint'], req_data['Message'][0])

        elif subscriber['Protocol'] == 'sqs':
            queue_url = None
            try:
                endpoint = subscriber['Endpoint']
                if 'sqs_queue_url' in subscriber:
                    queue_url = subscriber.get('sqs_queue_url')
                elif '://' in endpoint:
                    queue_url = endpoint
                else:
                    queue_name = endpoint.split(':')[5]
                    queue_url = aws_stack.get_sqs_queue_url(queue_name)
                    subscriber['sqs_queue_url'] = queue_url

                sqs_client = aws_stack.connect_to_service('sqs')
                sqs_client.send_message(
                    QueueUrl=queue_url,
                    MessageBody=create_sns_message_body(
                        subscriber, req_data, message_id),
                    MessageAttributes=create_sqs_message_attributes(
                        subscriber, message_attributes))
            except Exception as exc:
                LOG.warning('Unable to forward SNS message to SQS: %s %s' %
                            (exc, traceback.format_exc()))
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                               req_data, str(exc))
                if 'NonExistentQueue' in str(exc):
                    LOG.info(
                        'Removing non-existent queue "%s" subscribed to topic "%s"'
                        % (queue_url, topic_arn))
                    subscriptions.remove(subscriber)

        elif subscriber['Protocol'] == 'lambda':
            try:
                external_url = external_service_url('sns')
                unsubscribe_url = '%s/?Action=Unsubscribe&SubscriptionArn=%s' % (
                    external_url, subscriber['SubscriptionArn'])
                response = lambda_api.process_sns_notification(
                    subscriber['Endpoint'],
                    topic_arn,
                    subscriber['SubscriptionArn'],
                    message,
                    message_id,
                    message_attributes,
                    unsubscribe_url,
                    subject=req_data.get('Subject', [None])[0])
                if isinstance(response, Response):
                    response.raise_for_status()
                elif isinstance(response, FlaskResponse):
                    if response.status_code >= 400:
                        raise Exception('Error response (code %s): %s' %
                                        (response.status_code, response.data))
            except Exception as exc:
                LOG.warning(
                    'Unable to run Lambda function on SNS message: %s %s' %
                    (exc, traceback.format_exc()))
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                               req_data, str(exc))

        elif subscriber['Protocol'] in ['http', 'https']:
            msg_type = (req_data.get('Type') or ['Notification'])[0]
            try:
                message_body = create_sns_message_body(subscriber, req_data,
                                                       message_id)
            except Exception:
                continue
            try:
                response = requests.post(
                    subscriber['Endpoint'],
                    headers={
                        'Content-Type': 'text/plain',
                        # AWS headers according to
                        # https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-header
                        'x-amz-sns-message-type': msg_type,
                        'x-amz-sns-topic-arn': subscriber['TopicArn'],
                        'x-amz-sns-subscription-arn':
                        subscriber['SubscriptionArn'],
                        'User-Agent':
                        'Amazon Simple Notification Service Agent'
                    },
                    data=message_body,
                    verify=False)
                response.raise_for_status()
            except Exception as exc:
                LOG.info(
                    'Received error on sending SNS message, putting to DLQ (if configured): %s'
                    % exc)
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                               req_data, str(exc))

        elif subscriber['Protocol'] == 'application':
            try:
                sns_client = aws_stack.connect_to_service('sns')
                sns_client.publish(TargetArn=subscriber['Endpoint'],
                                   Message=message)
            except Exception as exc:
                LOG.warning(
                    'Unable to forward SNS message to SNS platform app: %s %s'
                    % (exc, traceback.format_exc()))
                sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                               req_data, str(exc))

        else:
            LOG.warning('Unexpected protocol "%s" for SNS subscription' %
                        subscriber['Protocol'])
Esempio n. 12
0
async def message_to_subscriber(message_id, message, topic_arn, req_data,
                                headers, subscription_arn, skip_checks,
                                sns_backend, subscriber, subscriptions):

    if subscription_arn not in [None, subscriber['SubscriptionArn']]:
        return

    filter_policy = json.loads(subscriber.get('FilterPolicy') or '{}')
    message_attributes = get_message_attributes(req_data)
    if not skip_checks and not check_filter_policy(filter_policy,
                                                   message_attributes):
        LOG.info('SNS filter policy %s does not match attributes %s' %
                 (filter_policy, message_attributes))
        return
    if subscriber['Protocol'] == 'sms':
        event = {
            'topic_arn': topic_arn,
            'endpoint': subscriber['Endpoint'],
            'message_content': req_data['Message'][0]
        }
        sns_backend.sms_messages.append(event)
        LOG.info('Delivering SMS message to %s: %s', subscriber['Endpoint'],
                 req_data['Message'][0])
        return

    elif subscriber['Protocol'] == 'sqs':
        queue_url = None

        try:
            endpoint = subscriber['Endpoint']

            if 'sqs_queue_url' in subscriber:
                queue_url = subscriber.get('sqs_queue_url')
            elif '://' in endpoint:
                queue_url = endpoint
            else:
                queue_name = endpoint.split(':')[5]
                queue_url = aws_stack.get_sqs_queue_url(queue_name)
                subscriber['sqs_queue_url'] = queue_url

            message_group_id = req_data.get(
                'MessageGroupId')[0] if req_data.get('MessageGroupId') else ''

            sqs_client = aws_stack.connect_to_service('sqs')

            # TODO remove this kwargs if we stop using ElasticMQ entirely
            kwargs = {
                'MessageGroupId': message_group_id
            } if SQS_BACKEND_IMPL == 'moto' else {}
            sqs_client.send_message(
                QueueUrl=queue_url,
                MessageBody=create_sns_message_body(subscriber, req_data,
                                                    message_id),
                MessageAttributes=create_sqs_message_attributes(
                    subscriber, message_attributes),
                MessageSystemAttributes=create_sqs_system_attributes(headers),
                **kwargs)
        except Exception as exc:
            LOG.info('Unable to forward SNS message to SQS: %s %s' %
                     (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                           req_data, str(exc))
            if 'NonExistentQueue' in str(exc):
                LOG.info(
                    'Removing non-existent queue "%s" subscribed to topic "%s"'
                    % (queue_url, topic_arn))
                subscriptions.remove(subscriber)
        return

    elif subscriber['Protocol'] == 'lambda':
        try:
            external_url = external_service_url('sns')
            unsubscribe_url = '%s/?Action=Unsubscribe&SubscriptionArn=%s' % (
                external_url, subscriber['SubscriptionArn'])
            response = lambda_api.process_sns_notification(
                subscriber['Endpoint'],
                topic_arn,
                subscriber['SubscriptionArn'],
                message,
                message_id,
                message_attributes,
                unsubscribe_url,
                subject=req_data.get('Subject', [None])[0])
            if isinstance(response, Response):
                response.raise_for_status()
            elif isinstance(response, FlaskResponse):
                if response.status_code >= 400:
                    raise Exception('Error response (code %s): %s' %
                                    (response.status_code, response.data))
        except Exception as exc:
            LOG.info('Unable to run Lambda function on SNS message: %s %s' %
                     (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                           req_data, str(exc))
        return

    elif subscriber['Protocol'] in ['http', 'https']:
        msg_type = (req_data.get('Type') or ['Notification'])[0]
        try:
            message_body = create_sns_message_body(subscriber, req_data,
                                                   message_id)
        except Exception:
            return
        try:
            response = requests.post(
                subscriber['Endpoint'],
                headers={
                    'Content-Type': 'text/plain',
                    # AWS headers according to
                    # https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-header
                    'x-amz-sns-message-type': msg_type,
                    'x-amz-sns-topic-arn': subscriber['TopicArn'],
                    'x-amz-sns-subscription-arn':
                    subscriber['SubscriptionArn'],
                    'User-Agent': 'Amazon Simple Notification Service Agent'
                },
                data=message_body,
                verify=False)
            response.raise_for_status()
        except Exception as exc:
            LOG.info(
                'Received error on sending SNS message, putting to DLQ (if configured): %s'
                % exc)
            sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                           req_data, str(exc))
        return

    elif subscriber['Protocol'] == 'application':
        try:
            sns_client = aws_stack.connect_to_service('sns')
            sns_client.publish(TargetArn=subscriber['Endpoint'],
                               Message=message)
        except Exception as exc:
            LOG.warning(
                'Unable to forward SNS message to SNS platform app: %s %s' %
                (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber['SubscriptionArn'],
                                           req_data, str(exc))
        return

    elif subscriber['Protocol'] == 'email':
        ses_client = aws_stack.connect_to_service('ses')
        if subscriber.get('Endpoint'):
            ses_client.verify_email_address(
                EmailAddress=subscriber.get('Endpoint'))
            ses_client.verify_email_address(
                EmailAddress='*****@*****.**')

            ses_client.send_email(
                Source='*****@*****.**',
                Message={
                    'Body': {
                        'Text': {
                            'Data': message
                        }
                    },
                    'Subject': {
                        'Data': 'SNS-Subscriber-Endpoint'
                    }
                },
                Destination={'ToAddresses': [subscriber.get('Endpoint')]})
    else:
        LOG.warning('Unexpected protocol "%s" for SNS subscription' %
                    subscriber['Protocol'])
Esempio n. 13
0
async def message_to_subscriber(
    message_id,
    message,
    topic_arn,
    req_data,
    headers,
    subscription_arn,
    skip_checks,
    sns_backend,
    subscriber,
    subscriptions,
):

    if subscription_arn not in [None, subscriber["SubscriptionArn"]]:
        return

    filter_policy = json.loads(subscriber.get("FilterPolicy") or "{}")
    message_attributes = get_message_attributes(req_data)
    if not skip_checks and not check_filter_policy(filter_policy,
                                                   message_attributes):
        LOG.info("SNS filter policy %s does not match attributes %s" %
                 (filter_policy, message_attributes))
        return
    if subscriber["Protocol"] == "sms":
        event = {
            "topic_arn": topic_arn,
            "endpoint": subscriber["Endpoint"],
            "message_content": req_data["Message"][0],
        }
        sns_backend.sms_messages.append(event)
        LOG.info(
            "Delivering SMS message to %s: %s",
            subscriber["Endpoint"],
            req_data["Message"][0],
        )
        return

    elif subscriber["Protocol"] == "sqs":
        queue_url = None

        try:
            endpoint = subscriber["Endpoint"]

            if "sqs_queue_url" in subscriber:
                queue_url = subscriber.get("sqs_queue_url")
            elif "://" in endpoint:
                queue_url = endpoint
            else:
                queue_name = endpoint.split(":")[5]
                queue_url = aws_stack.get_sqs_queue_url(queue_name)
                subscriber["sqs_queue_url"] = queue_url

            message_group_id = (req_data.get("MessageGroupId")[0]
                                if req_data.get("MessageGroupId") else "")

            sqs_client = aws_stack.connect_to_service("sqs")

            # TODO remove this kwargs if we stop using ElasticMQ entirely
            kwargs = {
                "MessageGroupId": message_group_id
            } if SQS_BACKEND_IMPL == "moto" else {}
            sqs_client.send_message(
                QueueUrl=queue_url,
                MessageBody=create_sns_message_body(subscriber, req_data,
                                                    message_id),
                MessageAttributes=create_sqs_message_attributes(
                    subscriber, message_attributes),
                MessageSystemAttributes=create_sqs_system_attributes(headers),
                **kwargs,
            )
        except Exception as exc:
            LOG.info("Unable to forward SNS message to SQS: %s %s" %
                     (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
            if "NonExistentQueue" in str(exc):
                LOG.info(
                    'Removing non-existent queue "%s" subscribed to topic "%s"'
                    % (queue_url, topic_arn))
                subscriptions.remove(subscriber)
        return

    elif subscriber["Protocol"] == "lambda":
        try:
            external_url = external_service_url("sns")
            unsubscribe_url = "%s/?Action=Unsubscribe&SubscriptionArn=%s" % (
                external_url,
                subscriber["SubscriptionArn"],
            )
            response = lambda_api.process_sns_notification(
                subscriber["Endpoint"],
                topic_arn,
                subscriber["SubscriptionArn"],
                message,
                message_id,
                message_attributes,
                unsubscribe_url,
                subject=req_data.get("Subject", [None])[0],
            )
            if isinstance(response, Response):
                response.raise_for_status()
            elif isinstance(response, FlaskResponse):
                if response.status_code >= 400:
                    raise Exception("Error response (code %s): %s" %
                                    (response.status_code, response.data))
        except Exception as exc:
            LOG.info("Unable to run Lambda function on SNS message: %s %s" %
                     (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] in ["http", "https"]:
        msg_type = (req_data.get("Type") or ["Notification"])[0]
        try:
            message_body = create_sns_message_body(subscriber, req_data,
                                                   message_id)
        except Exception:
            return
        try:
            response = requests.post(
                subscriber["Endpoint"],
                headers={
                    "Content-Type": "text/plain",
                    # AWS headers according to
                    # https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html#http-header
                    "x-amz-sns-message-type": msg_type,
                    "x-amz-sns-topic-arn": subscriber["TopicArn"],
                    "x-amz-sns-subscription-arn":
                    subscriber["SubscriptionArn"],
                    "User-Agent": "Amazon Simple Notification Service Agent",
                },
                data=message_body,
                verify=False,
            )
            response.raise_for_status()
        except Exception as exc:
            LOG.info(
                "Received error on sending SNS message, putting to DLQ (if configured): %s"
                % exc)
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] == "application":
        try:
            sns_client = aws_stack.connect_to_service("sns")
            sns_client.publish(TargetArn=subscriber["Endpoint"],
                               Message=message)
        except Exception as exc:
            LOG.warning(
                "Unable to forward SNS message to SNS platform app: %s %s" %
                (exc, traceback.format_exc()))
            sns_error_to_dead_letter_queue(subscriber["SubscriptionArn"],
                                           req_data, str(exc))
        return

    elif subscriber["Protocol"] == "email":
        ses_client = aws_stack.connect_to_service("ses")
        if subscriber.get("Endpoint"):
            ses_client.verify_email_address(
                EmailAddress=subscriber.get("Endpoint"))
            ses_client.verify_email_address(
                EmailAddress="*****@*****.**")

            ses_client.send_email(
                Source="*****@*****.**",
                Message={
                    "Body": {
                        "Text": {
                            "Data": message
                        }
                    },
                    "Subject": {
                        "Data": "SNS-Subscriber-Endpoint"
                    },
                },
                Destination={"ToAddresses": [subscriber.get("Endpoint")]},
            )
    else:
        LOG.warning('Unexpected protocol "%s" for SNS subscription' %
                    subscriber["Protocol"])