Make all responses to email API return a validation response. (#3650)

This commit is contained in:
Andrew Williamson 2016-10-04 11:53:58 +01:00 коммит произвёл GitHub
Родитель 0c43382ca5
Коммит be18b6cbdf
8 изменённых файлов: 79 добавлений и 13 удалений

Просмотреть файл

@ -125,7 +125,9 @@ CSP_IMG_SRC += (HTTP_GA_SRC,)
CSP_SCRIPT_SRC += (HTTP_GA_SRC, "'self'")
# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = 'totally-unsecure-string-for-local-development-goodness'
INBOUND_EMAIL_SECRET_KEY = 'totally-unsecure-secret-string'
# Validation key we need to send in POST response.
INBOUND_EMAIL_VALIDATION_KEY = 'totally-unsecure-validation-string'
# If you have settings you want to overload, put them in a local_settings.py.
try:

Просмотреть файл

@ -389,19 +389,24 @@ class TestReviewNotesViewSetCreateActivityEmailWaffleOff(TestCase):
@override_settings(ALLOWED_CLIENTS_EMAIL_API=['10.10.10.10'])
@override_settings(INBOUND_EMAIL_SECRET_KEY='SOME SECRET KEY')
@override_settings(INBOUND_EMAIL_VALIDATION_KEY='validation key')
class TestEmailApi(TestCase):
def get_request(self, data):
datastr = json.dumps(data)
req = req_factory_factory(reverse('inbound-email-api'))
req = req_factory_factory(reverse('inbound-email-api'), post=True)
req.META['REMOTE_ADDR'] = '10.10.10.10'
req.META['CONTENT_LENGTH'] = len(datastr)
req.META['CONTENT_TYPE'] = 'application/json'
req.POST = data
req.method = 'POST'
req._stream = StringIO.StringIO(datastr)
return req
def get_validation_request(self, data):
req = req_factory_factory(
url=reverse('inbound-email-api'), post=True, data=data)
req.META['REMOTE_ADDR'] = '10.10.10.10'
return req
def test_basic(self):
user = user_factory()
self.grant_permission(user, '*:*')
@ -414,6 +419,8 @@ class TestEmailApi(TestCase):
res = inbound_email(req)
assert res.status_code == 201
res.render()
assert res.content == '"validation key"'
logs = ActivityLog.objects.for_addons(addon)
assert logs.count() == 1
assert logs.get(action=amo.LOG.REVIEWER_REPLY_VERSION.id)
@ -442,8 +449,28 @@ class TestEmailApi(TestCase):
res = inbound_email(req)
_mock.assert_called_with(('something',))
assert res.status_code == 201
res.render()
assert res.content == '"validation key"'
def test_bad_request(self):
"""Test with no email body."""
res = inbound_email(self.get_request({'SecretKey': 'SOME SECRET KEY'}))
assert res.status_code == 400
@mock.patch('olympia.activity.tasks.process_email.apply_async')
def test_validation_response(self, _mock):
req = self.get_validation_request(
{'SecretKey': 'SOME SECRET KEY', 'Type': 'Validation'})
res = inbound_email(req)
assert not _mock.called
assert res.status_code == 200
res.render()
assert res.content == '"validation key"'
@mock.patch('olympia.activity.tasks.process_email.apply_async')
def test_validation_response_wrong_secret(self, _mock):
req = self.get_validation_request(
{'SecretKey': 'WRONG SECRET', 'Type': 'Validation'})
res = inbound_email(req)
assert not _mock.called
assert res.status_code == 403

Просмотреть файл

@ -82,11 +82,13 @@ class EmailCreationPermission(object):
# request.data isn't available at this point.
data = json.loads(request.body)
except ValueError:
data = {}
# Verification checks don't send JSON, but do send the key as POST.
data = request.POST
secret_key = data.get('SecretKey', '')
if not secret_key == settings.INBOUND_EMAIL_SECRET_KEY:
log.info('Invalid secret key [%s] provided' % (secret_key,))
log.info('Invalid secret key [%s] provided; data [%s]' % (
secret_key, data))
return False
remote_ip = request.META.get('REMOTE_ADDR', '')
@ -102,10 +104,17 @@ class EmailCreationPermission(object):
@authentication_classes(())
@permission_classes((EmailCreationPermission,))
def inbound_email(request):
validation_response = settings.INBOUND_EMAIL_VALIDATION_KEY
if request.data.get('Type', '') == 'Validation':
# Its just a verification check that the end-point is working.
return Response(data=validation_response,
status=status.HTTP_200_OK)
message = request.data.get('Message', None)
if not message:
raise ParseError(
detail='Message not present in the POST data.')
process_email.apply_async((message,))
return Response(status=status.HTTP_201_CREATED)
return Response(data=validation_response,
status=status.HTTP_201_CREATED)

Просмотреть файл

@ -16,5 +16,5 @@ urlpatterns = patterns(
url(r'^v3/internal/', include('olympia.internal_tools.urls')),
url(r'^v3/', include('olympia.signing.urls')),
url(r'^v3/statistics/', include('olympia.stats.api_urls')),
url(r'^v3/activity', include('olympia.activity.urls')),
url(r'^v3/activity/', include('olympia.activity.urls')),
)

Просмотреть файл

@ -33,6 +33,15 @@ EMAIL_HOST_USER = EMAIL_URL['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = EMAIL_URL['EMAIL_HOST_PASSWORD']
EMAIL_QA_WHITELIST = env.list('EMAIL_QA_WHITELIST')
# Filter IP addresses of allowed clients that can post email through the API.
ALLOWED_CLIENTS_EMAIL_API = env.list('ALLOWED_CLIENTS_EMAIL_API', default=[])
# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = env('INBOUND_EMAIL_SECRET_KEY', default='')
# Validation key we need to send in POST response.
INBOUND_EMAIL_VALIDATION_KEY = env('INBOUND_EMAIL_VALIDATION_KEY', default='')
# Domain emails should be sent to.
INBOUND_EMAIL_DOMAIN = env('INBOUND_EMAIL_DOMAIN', default=DOMAIN)
ENV = env('ENV')
DEBUG = False
DEBUG_PROPAGATE_EXCEPTIONS = False

Просмотреть файл

@ -20,6 +20,15 @@ EMAIL_BLACKLIST = env.list('EMAIL_BLACKLIST')
SEND_REAL_EMAIL = True
# Filter IP addresses of allowed clients that can post email through the API.
ALLOWED_CLIENTS_EMAIL_API = env.list('ALLOWED_CLIENTS_EMAIL_API', default=[])
# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = env('INBOUND_EMAIL_SECRET_KEY', default='')
# Validation key we need to send in POST response.
INBOUND_EMAIL_VALIDATION_KEY = env('INBOUND_EMAIL_VALIDATION_KEY', default='')
# Domain emails should be sent to.
INBOUND_EMAIL_DOMAIN = env('INBOUND_EMAIL_DOMAIN', default=DOMAIN)
ENV = env('ENV')
DEBUG = False
DEBUG_PROPAGATE_EXCEPTIONS = False

Просмотреть файл

@ -32,6 +32,16 @@ EMAIL_HOST_USER = EMAIL_URL['EMAIL_HOST_USER']
EMAIL_HOST_PASSWORD = EMAIL_URL['EMAIL_HOST_PASSWORD']
EMAIL_QA_WHITELIST = env.list('EMAIL_QA_WHITELIST')
# Filter IP addresses of allowed clients that can post email through the API.
ALLOWED_CLIENTS_EMAIL_API = env.list('ALLOWED_CLIENTS_EMAIL_API', default=[])
# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = env('INBOUND_EMAIL_SECRET_KEY', default='')
# Validation key we need to send in POST response.
INBOUND_EMAIL_VALIDATION_KEY = env('INBOUND_EMAIL_VALIDATION_KEY', default='')
# Domain emails should be sent to.
INBOUND_EMAIL_DOMAIN = env('INBOUND_EMAIL_DOMAIN', default=DOMAIN)
ENV = env('ENV')
DEBUG = False
DEBUG_PROPAGATE_EXCEPTIONS = False

Просмотреть файл

@ -217,14 +217,14 @@ MOBILE_DOMAIN = 'm.%s' % DOMAIN
# The full url of the mobile site.
MOBILE_SITE_URL = 'http://%s' % MOBILE_DOMAIN
# Filter IP addresses of the allowed clients that can post email
# through the API.
# Filter IP addresses of allowed clients that can post email through the API.
ALLOWED_CLIENTS_EMAIL_API = env.list('ALLOWED_CLIENTS_EMAIL_API', default=[])
# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = env('INBOUND_EMAIL_SECRET_KEY', default='')
INBOUND_EMAIL_DOMAIN = DOMAIN
# Validation key we need to send in POST response.
INBOUND_EMAIL_VALIDATION_KEY = env('INBOUND_EMAIL_VALIDATION_KEY', default='')
# Domain emails should be sent to.
INBOUND_EMAIL_DOMAIN = env('INBOUND_EMAIL_DOMAIN', default=DOMAIN)
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"