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)
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)
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, )
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)
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])
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)
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])
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
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'])
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"])
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'])
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'])
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"])