def test_verify_subscription_notification(self, mock): """Test the verification of a valid subscription notification""" mock.return_value = self.pemfile notification = loader('subscriptionconfirmation') result = utils.verify_notification(notification) self.assertTrue(result)
def test_subscription_verification_failure(self, mock): """Test the failure of an invalid subscription notification""" mock.return_value = self.pemfile notification = loader('subscriptionconfirmation') notification['TopicArn'] = 'BadArn' result = utils.verify_notification(notification) self.assertFalse(result)
def endpoint(request): """Endpoint that SNS accesses. Includes logic verifying request""" # pylint: disable=too-many-return-statements,too-many-branches # In order to 'hide' the endpoint, all non-POST requests should return # the site's default HTTP404 if request.method != 'POST': raise Http404 # If necessary, check that the topic is correct if hasattr(settings, 'BOUNCY_TOPIC_ARN'): # Confirm that the proper topic header was sent if 'HTTP_X_AMZ_SNS_TOPIC_ARN' not in request.META: return HttpResponseBadRequest('No TopicArn Header') # Check to see if the topic is in the settings # Because you can have bounces and complaints coming from multiple # topics, BOUNCY_TOPIC_ARN is a list if (not request.META['HTTP_X_AMZ_SNS_TOPIC_ARN'] in settings.BOUNCY_TOPIC_ARN): return HttpResponseBadRequest('Bad Topic') # Load the JSON POST Body if isinstance(request.body, str): # requests return str in python 2.7 request_body = request.body else: # and return bytes in python 3.4 request_body = request.body.decode() try: data = json.loads(request_body) except ValueError: logger.warning('Notification Not Valid JSON: {}'.format(request_body)) return HttpResponseBadRequest('Not Valid JSON') # Ensure that the JSON we're provided contains all the keys we expect # Comparison code from http://stackoverflow.com/questions/1285911/ if not set(VITAL_NOTIFICATION_FIELDS) <= set(data): logger.warning('Request Missing Necessary Keys') return HttpResponseBadRequest('Request Missing Necessary Keys') # Ensure that the type of notification is one we'll accept if not data['Type'] in ALLOWED_TYPES: logger.info('Notification Type Not Known %s', data['Type']) return HttpResponseBadRequest('Unknown Notification Type') # Confirm that the signing certificate is hosted on a correct domain # AWS by default uses sns.{region}.amazonaws.com # On the off chance you need this to be a different domain, allow the # regex to be overridden in settings domain = urlparse(data['SigningCertURL']).netloc pattern = getattr(settings, 'BOUNCY_CERT_DOMAIN_REGEX', r"sns.[a-z0-9\-]+.amazonaws.com$") if not re.search(pattern, domain): logger.warning('Improper Certificate Location %s', data['SigningCertURL']) return HttpResponseBadRequest('Improper Certificate Location') # Verify that the notification is signed by Amazon if (getattr(settings, 'BOUNCY_VERIFY_CERTIFICATE', True) and not verify_notification(data)): logger.error('Verification Failure %s', ) return HttpResponseBadRequest('Improper Signature') # Send a signal to say a valid notification has been received signals.notification.send(sender='bouncy_endpoint', notification=data, request=request) # Handle subscription-based messages. if data['Type'] == 'SubscriptionConfirmation': # Allow the disabling of the auto-subscription feature if not getattr(settings, 'BOUNCY_AUTO_SUBSCRIBE', True): raise Http404 return approve_subscription(data) elif data['Type'] == 'UnsubscribeConfirmation': # We won't handle unsubscribe requests here. Return a 200 status code # so Amazon won't redeliver the request. If you want to remove this # endpoint, remove it either via the API or the AWS Console logger.info('UnsubscribeConfirmation Not Handled') return HttpResponse('UnsubscribeConfirmation Not Handled') try: message = json.loads(data['Message']) except ValueError: # This message is not JSON. But we need to return a 200 status code # so that Amazon doesn't attempt to deliver the message again logger.info('Non-Valid JSON Message Received') return HttpResponse('Message is not valid JSON') return process_message(message, data)
def endpoint(request): """Endpoint that SNS accesses. Includes logic verifying request""" # pylint: disable=too-many-return-statements,too-many-branches # In order to 'hide' the endpoint, all non-POST requests should return # the site's default HTTP404 if request.method != 'POST': raise Http404 # If necessary, check that the topic is correct if hasattr(settings, 'BOUNCY_TOPIC_ARN'): # Confirm that the proper topic header was sent if 'HTTP_X_AMZ_SNS_TOPIC_ARN' not in request.META: return HttpResponseBadRequest('No TopicArn Header') # Check to see if the topic is in the settings # Because you can have bounces and complaints coming from multiple # topics, BOUNCY_TOPIC_ARN is a list if (not request.META['HTTP_X_AMZ_SNS_TOPIC_ARN'] in settings.BOUNCY_TOPIC_ARN): return HttpResponseBadRequest('Bad Topic') # Load the JSON POST Body if isinstance(request.body, str): # requests return str in python 2.7 request_body = request.body else: # and return bytes in python 3.4 request_body = request.body.decode() try: data = json.loads(request_body) except ValueError: logger.warning('Notification Not Valid JSON: {}'.format(request_body)) return HttpResponseBadRequest('Not Valid JSON') # Ensure that the JSON we're provided contains all the keys we expect # Comparison code from http://stackoverflow.com/questions/1285911/ if not set(VITAL_NOTIFICATION_FIELDS) <= set(data): logger.warning('Request Missing Necessary Keys') return HttpResponseBadRequest('Request Missing Necessary Keys') # Ensure that the type of notification is one we'll accept if not data['Type'] in ALLOWED_TYPES: logger.info('Notification Type Not Known %s', data['Type']) return HttpResponseBadRequest('Unknown Notification Type') # Confirm that the signing certificate is hosted on a correct domain # AWS by default uses sns.{region}.amazonaws.com # On the off chance you need this to be a different domain, allow the # regex to be overridden in settings domain = urlparse(data['SigningCertURL']).netloc pattern = getattr( settings, 'BOUNCY_CERT_DOMAIN_REGEX', r"sns.[a-z0-9\-]+.amazonaws.com$" ) if not re.search(pattern, domain): logger.warning( 'Improper Certificate Location %s', data['SigningCertURL']) return HttpResponseBadRequest('Improper Certificate Location') # Verify that the notification is signed by Amazon if (getattr(settings, 'BOUNCY_VERIFY_CERTIFICATE', True) and not verify_notification(data)): logger.error('Verification Failure %s', ) return HttpResponseBadRequest('Improper Signature') # Send a signal to say a valid notification has been received signals.notification.send( sender='bouncy_endpoint', notification=data, request=request) # Handle subscription-based messages. if data['Type'] == 'SubscriptionConfirmation': # Allow the disabling of the auto-subscription feature if not getattr(settings, 'BOUNCY_AUTO_SUBSCRIBE', True): raise Http404 return approve_subscription(data) elif data['Type'] == 'UnsubscribeConfirmation': # We won't handle unsubscribe requests here. Return a 200 status code # so Amazon won't redeliver the request. If you want to remove this # endpoint, remove it either via the API or the AWS Console logger.info('UnsubscribeConfirmation Not Handled') return HttpResponse('UnsubscribeConfirmation Not Handled') try: message = json.loads(data['Message']) except ValueError: # This message is not JSON. But we need to return a 200 status code # so that Amazon doesn't attempt to deliver the message again logger.info('Non-Valid JSON Message Received') return HttpResponse('Message is not valid JSON') return process_message(message, data)
def test_verify_notification(self, mock): """Test the verification of a valid notification""" mock.return_value = self.pemfile result = utils.verify_notification(self.notification) self.assertTrue(result)