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