def publish_message(topic_arn, req_data, subscription_arn=None): message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS.get(topic_arn, []): 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': 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 try: 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: return make_error(message=str(exc), code=400) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification(subscriber['Endpoint'], topic_arn, subscriber['SubscriptionArn'], message, message_attributes, subject=req_data.get( 'Subject', [None])[0]) 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 as exc: return make_error(message=str(exc), code=400) 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) else: LOGGER.warning('Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol'])
def publish_message(topic_arn, req_data): message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS.get(topic_arn, []): filter_policy = json.loads(subscriber.get('FilterPolicy', '{}')) message_attributes = get_message_attributes(req_data) if not check_filter_policy(filter_policy, message_attributes): continue if subscriber['Protocol'] == 'sqs': 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 try: 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: return make_error(message=str(exc), code=400) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification( subscriber['Endpoint'], topic_arn, subscriber['SubscriptionArn'], message, message_attributes, subject=req_data.get('Subject', [None])[0] ) elif subscriber['Protocol'] in ['http', 'https']: try: message_body = create_sns_message_body(subscriber, req_data) except Exception as exc: return make_error(message=str(exc), code=400) requests.post( subscriber['Endpoint'], headers={ 'Content-Type': 'text/plain', 'x-amz-sns-message-type': 'Notification' }, data=message_body ) else: LOGGER.warning('Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol'])
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'])
def forward_request(self, method, path, data, headers): if method == 'POST' and path == '/': req_data = urlparse.parse_qs(to_str(data)) req_action = req_data['Action'][0] topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') if topic_arn: topic_arn = topic_arn[0] do_create_topic(topic_arn) if req_action == 'SetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error( message='Unable to find subscription for given ARN', code=400) attr_name = req_data['AttributeName'][0] attr_value = req_data['AttributeValue'][0] sub[attr_name] = attr_value return make_response(req_action) elif req_action == 'GetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error( message='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): content += '<entry><key>%s</key><value>%s</value></entry>\n' % ( key, value) content += '</Attributes>' return make_response(req_action, content=content) elif req_action == 'Subscribe': if 'Endpoint' not in req_data: return make_error( message='Endpoint not specified in subscription', code=400) elif req_action == 'Unsubscribe': if 'SubscriptionArn' not in req_data: return make_error( message= 'SubscriptionArn not specified in unsubscribe request', code=400) do_unsubscribe(req_data.get('SubscriptionArn')[0]) elif req_action == 'Publish': message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS[topic_arn]: if subscriber['Protocol'] == 'sqs': 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)) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification( subscriber['Endpoint'], topic_arn, message, subject=req_data.get('Subject')) elif subscriber['Protocol'] in ['http', 'https']: requests.post(subscriber['Endpoint'], headers={ 'Content-Type': 'text/plain', 'x-amz-sns-message-type': 'Notification' }, data=create_sns_message_body( subscriber, req_data)) else: LOGGER.warning( 'Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol']) # return response here because we do not want the request to be forwarded to SNS return make_response(req_action) return True
def forward_request(self, method, path, data, headers): if method == 'POST' and path == '/': req_data = urlparse.parse_qs(to_str(data)) req_action = req_data['Action'][0] topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') if topic_arn: topic_arn = topic_arn[0] do_create_topic(topic_arn) if req_action == 'SetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error(message='Unable to find subscription for given ARN', code=400) attr_name = req_data['AttributeName'][0] attr_value = req_data['AttributeValue'][0] sub[attr_name] = attr_value return make_response(req_action) elif req_action == 'GetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error(message='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): content += '<entry><key>%s</key><value>%s</value></entry>\n' % (key, value) content += '</Attributes>' return make_response(req_action, content=content) elif req_action == 'Subscribe': if 'Endpoint' not in req_data: return make_error(message='Endpoint not specified in subscription', code=400) elif req_action == 'Unsubscribe': if 'SubscriptionArn' not in req_data: return make_error(message='SubscriptionArn not specified in unsubscribe request', code=400) do_unsubscribe(req_data.get('SubscriptionArn')[0]) elif req_action == 'Publish': message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS[topic_arn]: filter_policy = json.loads(subscriber.get('FilterPolicy', '{}')) message_attributes = get_message_attributes(req_data) if check_filter_policy(filter_policy, message_attributes): if subscriber['Protocol'] == 'sqs': 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 try: sqs_client.send_message( QueueUrl=queue_url, MessageBody=create_sns_message_body(subscriber, req_data) ) except Exception as exc: return make_error(message=str(exc), code=400) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification( subscriber['Endpoint'], topic_arn, message, subject=req_data.get('Subject', [None])[0] ) elif subscriber['Protocol'] in ['http', 'https']: try: message_body = create_sns_message_body(subscriber, req_data) except Exception as exc: return make_error(message=str(exc), code=400) requests.post( subscriber['Endpoint'], headers={ 'Content-Type': 'text/plain', 'x-amz-sns-message-type': 'Notification' }, data=message_body ) else: LOGGER.warning('Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol']) # return response here because we do not want the request to be forwarded to SNS return make_response(req_action) return True
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 update_sns(method, path, data, headers, response=None, return_forward_info=False): if return_forward_info: if method == 'POST' and path == '/': req_data = urlparse.parse_qs(data) topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') if topic_arn: topic_arn = topic_arn[0] if topic_arn not in SNS_SUBSCRIPTIONS: SNS_SUBSCRIPTIONS[topic_arn] = [] if 'Subscribe' in req_data['Action']: subscription = { # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html 'TopicArn': topic_arn, 'Endpoint': req_data['Endpoint'][0], 'Protocol': req_data['Protocol'][0], 'RawMessageDelivery': 'false' } SNS_SUBSCRIPTIONS[topic_arn].append(subscription) elif 'Publish' in req_data['Action']: message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS[topic_arn]: if subscriber['Protocol'] == 'sqs': queue_name = subscriber['Endpoint'].split(':')[5] queue_url = subscriber.get('sqs_queue_url') if not queue_url: 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)) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification( subscriber['Endpoint'], topic_arn, message, subject=req_data.get('Subject')) elif subscriber['Protocol'] == 'http': requests.post(subscriber['Endpoint'], headers={ 'Content-Type': 'text/plain', 'x-amz-sns-message-type': 'Notification' }, data=json.dumps({ 'Type': 'Notification', 'Message': message, })) else: LOGGER.warning( 'Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol']) # return response here because we do not want the request to be forwarded to SNS response = Response() response._content = """<PublishResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/"> <PublishResult><MessageId>n/a</MessageId></PublishResult> <ResponseMetadata><RequestId>n/a</RequestId></ResponseMetadata></PublishResponse>""" response.status_code = 200 return response return True
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 update_sns(method, path, data, headers, response=None, return_forward_info=False): if return_forward_info: if method == 'POST' and path == '/': req_data = urlparse.parse_qs(data) req_action = req_data['Action'][0] topic_arn = req_data.get('TargetArn') or req_data.get('TopicArn') if topic_arn: topic_arn = topic_arn[0] if topic_arn not in SNS_SUBSCRIPTIONS: SNS_SUBSCRIPTIONS[topic_arn] = [] if req_action == 'SetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error( message='Unable to find subscription for given ARN', code=400) attr_name = req_data['AttributeName'][0] attr_value = req_data['AttributeValue'][0] sub[attr_name] = attr_value return make_response(req_action) elif req_action == 'GetSubscriptionAttributes': sub = get_subscription_by_arn(req_data['SubscriptionArn'][0]) if not sub: return make_error( message='Unable to find subscription for given ARN', code=400) content = '<Attributes>' for key, value in sub.items(): content += '<entry><key>%s</key><value>%s</value></entry>\n' % ( key, value) content += '</Attributes>' return make_response(req_action, content=content) elif req_action == 'Subscribe': if 'Endpoint' not in req_data: return make_error( message='Endpoint not specified in subscription', code=400) elif req_action == 'Publish': message = req_data['Message'][0] sqs_client = aws_stack.connect_to_service('sqs') for subscriber in SNS_SUBSCRIPTIONS[topic_arn]: if subscriber['Protocol'] == 'sqs': queue_name = subscriber['Endpoint'].split(':')[5] queue_url = subscriber.get('sqs_queue_url') if not queue_url: 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)) elif subscriber['Protocol'] == 'lambda': lambda_api.process_sns_notification( subscriber['Endpoint'], topic_arn, message, subject=req_data.get('Subject')) elif subscriber['Protocol'] == 'http': requests.post(subscriber['Endpoint'], headers={ 'Content-Type': 'text/plain', 'x-amz-sns-message-type': 'Notification' }, data=json.dumps({ 'Type': 'Notification', 'Message': message, })) else: LOGGER.warning( 'Unexpected protocol "%s" for SNS subscription' % subscriber['Protocol']) # return response here because we do not want the request to be forwarded to SNS return make_response(req_action) return True else: # This branch is executed by the proxy after we've already received a # response from the backend, hence we can utilize the "reponse" variable here if method == 'POST' and path == '/': req_data = urlparse.parse_qs(data) req_action = req_data['Action'][0] if req_action == 'Subscribe' and response.status_code < 400: response_data = xmltodict.parse(response.content) topic_arn = (req_data.get('TargetArn') or req_data.get('TopicArn'))[0] sub_arn = response_data['SubscribeResponse'][ 'SubscribeResult']['SubscriptionArn'] subscription = { # http://docs.aws.amazon.com/cli/latest/reference/sns/get-subscription-attributes.html 'TopicArn': topic_arn, 'Endpoint': req_data['Endpoint'][0], 'Protocol': req_data['Protocol'][0], 'SubscriptionArn': sub_arn, 'RawMessageDelivery': 'false' } SNS_SUBSCRIPTIONS[topic_arn].append(subscription)