Merge pull request #1166 from terrycain/sns_full

Complete SNS API
This commit is contained in:
Jack Danger 2017-09-21 22:30:32 -07:00 committed by GitHub
commit 4213f88c6b
7 changed files with 335 additions and 1 deletions

View file

@ -77,6 +77,7 @@ class Subscription(BaseModel):
self.protocol = protocol
self.arn = make_arn_for_subscription(self.topic.arn)
self.attributes = {}
self.confirmed = False
def publish(self, message, message_id):
if self.protocol == 'sqs':
@ -172,12 +173,18 @@ class SNSBackend(BaseBackend):
self.applications = {}
self.platform_endpoints = {}
self.region_name = region_name
self.sms_attributes = {}
self.opt_out_numbers = ['+447420500600', '+447420505401', '+447632960543', '+447632960028', '+447700900149', '+447700900550', '+447700900545', '+447700900907']
self.permissions = {}
def reset(self):
region_name = self.region_name
self.__dict__ = {}
self.__init__(region_name)
def update_sms_attributes(self, attrs):
self.sms_attributes.update(attrs)
def create_topic(self, name):
topic = Topic(name, self)
self.topics[topic.arn] = topic

View file

@ -1,5 +1,7 @@
from __future__ import unicode_literals
import json
import re
from collections import defaultdict
from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
@ -7,11 +9,17 @@ from .models import sns_backends
class SNSResponse(BaseResponse):
SMS_ATTR_REGEX = re.compile(r'^attributes\.entry\.(?P<index>\d+)\.(?P<type>key|value)$')
OPT_OUT_PHONE_NUMBER_REGEX = re.compile(r'^\+?\d+$')
@property
def backend(self):
return sns_backends[self.region]
def _error(self, code, message, sender='Sender'):
template = self.response_template(ERROR_RESPONSE)
return template.render(code=code, message=message, sender=sender)
def _get_attributes(self):
attributes = self._get_list_prefix('Attributes.entry')
return dict(
@ -459,6 +467,131 @@ class SNSResponse(BaseResponse):
template = self.response_template(SET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE)
return template.render()
def set_sms_attributes(self):
# attributes.entry.1.key
# attributes.entry.1.value
# to
# 1: {key:X, value:Y}
temp_dict = defaultdict(dict)
for key, value in self.querystring.items():
match = self.SMS_ATTR_REGEX.match(key)
if match is not None:
temp_dict[match.group('index')][match.group('type')] = value[0]
# 1: {key:X, value:Y}
# to
# X: Y
# All of this, just to take into account when people provide invalid stuff.
result = {}
for item in temp_dict.values():
if 'key' in item and 'value' in item:
result[item['key']] = item['value']
self.backend.update_sms_attributes(result)
template = self.response_template(SET_SMS_ATTRIBUTES_TEMPLATE)
return template.render()
def get_sms_attributes(self):
filter_list = set()
for key, value in self.querystring.items():
if key.startswith('attributes.member.1'):
filter_list.add(value[0])
if len(filter_list) > 0:
result = {k: v for k, v in self.backend.sms_attributes.items() if k in filter_list}
else:
result = self.backend.sms_attributes
template = self.response_template(GET_SMS_ATTRIBUTES_TEMPLATE)
return template.render(attributes=result)
def check_if_phone_number_is_opted_out(self):
number = self._get_param('phoneNumber')
if self.OPT_OUT_PHONE_NUMBER_REGEX.match(number) is None:
error_response = self._error(
code='InvalidParameter',
message='Invalid parameter: PhoneNumber Reason: input incorrectly formatted'
)
return error_response, dict(status=400)
# There should be a nicer way to set if a nubmer has opted out
template = self.response_template(CHECK_IF_OPTED_OUT_TEMPLATE)
return template.render(opt_out=str(number.endswith('99')).lower())
def list_phone_numbers_opted_out(self):
template = self.response_template(LIST_OPTOUT_TEMPLATE)
return template.render(opt_outs=self.backend.opt_out_numbers)
def opt_in_phone_number(self):
number = self._get_param('phoneNumber')
try:
self.backend.opt_out_numbers.remove(number)
except ValueError:
pass
template = self.response_template(OPT_IN_NUMBER_TEMPLATE)
return template.render()
def add_permission(self):
arn = self._get_param('TopicArn')
label = self._get_param('Label')
accounts = self._get_multi_param('AWSAccountId.member.')
action = self._get_multi_param('ActionName.member.')
if arn not in self.backend.topics:
error_response = self._error('NotFound', 'Topic does not exist')
return error_response, dict(status=404)
key = (arn, label)
self.backend.permissions[key] = {'accounts': accounts, 'action': action}
template = self.response_template(ADD_PERMISSION_TEMPLATE)
return template.render()
def remove_permission(self):
arn = self._get_param('TopicArn')
label = self._get_param('Label')
if arn not in self.backend.topics:
error_response = self._error('NotFound', 'Topic does not exist')
return error_response, dict(status=404)
try:
key = (arn, label)
del self.backend.permissions[key]
except KeyError:
pass
template = self.response_template(DEL_PERMISSION_TEMPLATE)
return template.render()
def confirm_subscription(self):
arn = self._get_param('TopicArn')
if arn not in self.backend.topics:
error_response = self._error('NotFound', 'Topic does not exist')
return error_response, dict(status=404)
# Once Tokens are stored by the `subscribe` endpoint and distributed
# to the client somehow, then we can check validity of tokens
# presented to this method. The following code works, all thats
# needed is to perform a token check and assign that value to the
# `already_subscribed` variable.
#
# token = self._get_param('Token')
# auth = self._get_param('AuthenticateOnUnsubscribe')
# if already_subscribed:
# error_response = self._error(
# code='AuthorizationError',
# message='Subscription already confirmed'
# )
# return error_response, dict(status=400)
template = self.response_template(CONFIRM_SUBSCRIPTION_TEMPLATE)
return template.render(sub_arn='{0}:68762e72-e9b1-410a-8b3b-903da69ee1d5'.format(arn))
CREATE_TOPIC_TEMPLATE = """<CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<CreateTopicResult>
@ -758,3 +891,85 @@ SET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE = """<SetSubscriptionAttributesResponse xml
<RequestId>a8763b99-33a7-11df-a9b7-05d48da6f042</RequestId>
</ResponseMetadata>
</SetSubscriptionAttributesResponse>"""
SET_SMS_ATTRIBUTES_TEMPLATE = """<SetSMSAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<SetSMSAttributesResult/>
<ResponseMetadata>
<RequestId>26332069-c04a-5428-b829-72524b56a364</RequestId>
</ResponseMetadata>
</SetSMSAttributesResponse>"""
GET_SMS_ATTRIBUTES_TEMPLATE = """<GetSMSAttributesResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<GetSMSAttributesResult>
<attributes>
{% for name, value in attributes.items() %}
<entry>
<key>{{ name }}</key>
<value>{{ value }}</value>
</entry>
{% endfor %}
</attributes>
</GetSMSAttributesResult>
<ResponseMetadata>
<RequestId>287f9554-8db3-5e66-8abc-c76f0186db7e</RequestId>
</ResponseMetadata>
</GetSMSAttributesResponse>"""
CHECK_IF_OPTED_OUT_TEMPLATE = """<CheckIfPhoneNumberIsOptedOutResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<CheckIfPhoneNumberIsOptedOutResult>
<isOptedOut>{{ opt_out }}</isOptedOut>
</CheckIfPhoneNumberIsOptedOutResult>
<ResponseMetadata>
<RequestId>287f9554-8db3-5e66-8abc-c76f0186db7e</RequestId>
</ResponseMetadata>
</CheckIfPhoneNumberIsOptedOutResponse>"""
ERROR_RESPONSE = """<ErrorResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<Error>
<Type>{{ sender }}</Type>
<Code>{{ code }}</Code>
<Message>{{ message }}</Message>
</Error>
<RequestId>9dd01905-5012-5f99-8663-4b3ecd0dfaef</RequestId>
</ErrorResponse>"""
LIST_OPTOUT_TEMPLATE = """<ListPhoneNumbersOptedOutResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<ListPhoneNumbersOptedOutResult>
<phoneNumbers>
{% for item in opt_outs %}
<member>{{ item }}</member>
{% endfor %}
</phoneNumbers>
</ListPhoneNumbersOptedOutResult>
<ResponseMetadata>
<RequestId>985e196d-a237-51b6-b33a-4b5601276b38</RequestId>
</ResponseMetadata>
</ListPhoneNumbersOptedOutResponse>"""
OPT_IN_NUMBER_TEMPLATE = """<OptInPhoneNumberResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<OptInPhoneNumberResult/>
<ResponseMetadata>
<RequestId>4c61842c-0796-50ef-95ac-d610c0bc8cf8</RequestId>
</ResponseMetadata>
</OptInPhoneNumberResponse>"""
ADD_PERMISSION_TEMPLATE = """<AddPermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<ResponseMetadata>
<RequestId>c046e713-c5ff-5888-a7bc-b52f0e4f1299</RequestId>
</ResponseMetadata>
</AddPermissionResponse>"""
DEL_PERMISSION_TEMPLATE = """<RemovePermissionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<ResponseMetadata>
<RequestId>e767cc9f-314b-5e1b-b283-9ea3fd4e38a3</RequestId>
</ResponseMetadata>
</RemovePermissionResponse>"""
CONFIRM_SUBSCRIPTION_TEMPLATE = """<ConfirmSubscriptionResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<ConfirmSubscriptionResult>
<SubscriptionArn>{{ sub_arn }}</SubscriptionArn>
</ConfirmSubscriptionResult>
<ResponseMetadata>
<RequestId>16eb4dde-7b3c-5b3e-a22a-1fe2a92d3293</RequestId>
</ResponseMetadata>
</ConfirmSubscriptionResponse>"""