diff --git a/settings.py b/settings.py index 17dc8a8948..e784b2d410 100644 --- a/settings.py +++ b/settings.py @@ -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: diff --git a/src/olympia/activity/tests/test_views.py b/src/olympia/activity/tests/test_views.py index e2db6f51e1..a3f41dd6d8 100644 --- a/src/olympia/activity/tests/test_views.py +++ b/src/olympia/activity/tests/test_views.py @@ -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 diff --git a/src/olympia/activity/views.py b/src/olympia/activity/views.py index 8d8c92f2a8..b98f1399d2 100644 --- a/src/olympia/activity/views.py +++ b/src/olympia/activity/views.py @@ -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) diff --git a/src/olympia/api/urls.py b/src/olympia/api/urls.py index f7b8de6648..b512ee0876 100644 --- a/src/olympia/api/urls.py +++ b/src/olympia/api/urls.py @@ -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')), ) diff --git a/src/olympia/conf/dev/settings.py b/src/olympia/conf/dev/settings.py index c028f634bb..54be3e34f1 100644 --- a/src/olympia/conf/dev/settings.py +++ b/src/olympia/conf/dev/settings.py @@ -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 diff --git a/src/olympia/conf/prod/settings.py b/src/olympia/conf/prod/settings.py index 2f19fc04c0..658cd1c1a1 100644 --- a/src/olympia/conf/prod/settings.py +++ b/src/olympia/conf/prod/settings.py @@ -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 diff --git a/src/olympia/conf/stage/settings.py b/src/olympia/conf/stage/settings.py index b40303f885..5d68cbce5d 100644 --- a/src/olympia/conf/stage/settings.py +++ b/src/olympia/conf/stage/settings.py @@ -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 diff --git a/src/olympia/lib/settings_base.py b/src/olympia/lib/settings_base.py index b269b1c3fb..32aed23a4a 100644 --- a/src/olympia/lib/settings_base.py +++ b/src/olympia/lib/settings_base.py @@ -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/"