예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
 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)
예제 #8
0
 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)