def obj_create(self, bundle, request, **kwargs):
        form = EventForm(bundle.data, request_encoding=request.encoding)
        if not form.is_valid():
            log.info("Event invalid.")
            raise self.form_errors(form)

        notification = form.cleaned_data["notification"]
        transaction = form.cleaned_data["transaction"]
        # TODO: use token checker here when supported.
        if notification["new_status"] != transaction.status:
            old_status = transaction.status
            transaction.status = notification["new_status"]
            transaction.save()

            log_cef(
                "Transaction change success",
                request,
                severity=7,
                cs6Label="old",
                cs6=STATUSES_INVERTED[old_status],
                cs7Label="new",
                cs7=STATUSES_INVERTED[transaction.status],
            )
            log.info("Transaction {0} changed to {1} from {2}".format(transaction.pk, transaction.status, old_status))

        return bundle
Example #2
0
    def create(self, request):
        form = EventForm(request.DATA)
        param = form.data.get('param')

        if not form.is_valid():
            log.info('Notification invalid: {0}'.format(param))
            return self.form_errors([form])

        cleaned = form.cleaned_data
        transaction = cleaned['param']

        # Verify this against Boku, this will raise errors if there's
        # an issue.
        verify(transaction, cleaned['amount'], cleaned['currency'])

        old_status = transaction.status
        # For the moment assume that all notifications that come in
        # are complete.
        transaction.status = STATUS_COMPLETED
        transaction.amount = cleaned['amount']
        transaction.currency = cleaned['currency']
        transaction.uid_support = cleaned['trx_id']
        transaction.save()

        log_cef('Transaction change success', request, severity=7,
                cs6Label='old', cs6=STATUSES_INVERTED[old_status],
                cs7Label='new', cs7=STATUSES_INVERTED[transaction.status])
        log.info('Transaction {0} changed to {1} from {2}'
                 .format(transaction.pk, transaction.status,
                         old_status))
        return HttpResponse(200, '')
Example #3
0
def verify_pin(request):
    form = PinForm(data=request.DATA)

    if form.is_valid():
        buyer = form.cleaned_data['buyer']
        valid = False
        locked = False

        if buyer.pin_confirmed:
            # Note that the incr_lockout and clear_lockout methods
            # trigger saves on the object. You should not do a save
            # in this view as well for fear of stomping on the save
            # caused by those methods.
            if buyer.locked_out:
                log_cef('Attempted access to locked out account: %s'
                        % buyer.uuid, request, severity=1)
                locked = True

            else:
                valid = buyer.pin == form.cleaned_data['pin']
                if not valid:
                    locked = buyer.incr_lockout()
                    if locked:
                        locked = True
                        log_cef('Locked out account: %s' % buyer.uuid,
                                request, severity=1)
                else:
                    buyer.clear_lockout(clear_was_locked=True)

        output = VerifiedSerializer(instance=buyer, valid=valid, locked=locked)
        return Response(output.data)

    raise FormError(form.errors)
Example #4
0
    def clean(self):
        fields = self.fields.keys()
        # If the provider is already set, don't allow it to be changed.
        if self.old.get('provider'):
            del fields[fields.index('provider')]

        keys = set(self.data.keys()).difference(set(fields))
        if keys:
            raise forms.ValidationError(
                'Cannot alter fields: {0}'.format(', '.join(keys)))

        old_text = constants.STATUSES_INVERTED.get(self.old['status'])
        new_text = constants.STATUSES_INVERTED.get(
            self.data.get('status', self.old['status']))

        try:
            check_status(self.old, self.data)
        except forms.ValidationError:
            log_cef('Transaction change failure', self.request, severity=7,
                    cs6Label='old', cs6=old_text, cs7Label='new', cs7=new_text)
            raise

        if new_text != old_text:
            log_cef('Transaction change success', self.request,
                    cs6Label='old', cs6=old_text, cs7Label='new', cs7=new_text)
        return self.cleaned_data
Example #5
0
    def obj_create(self, bundle, request, **kwargs):
        form = EventForm(bundle.data, request_encoding=request.encoding)
        if not form.is_valid():
            log.info('Event invalid.')
            raise self.form_errors(form)

        notification = form.cleaned_data['notification']
        transaction = form.cleaned_data['transaction']
        # TODO: use token checker here when supported.
        if notification['new_status'] != transaction.status:
            old_status = transaction.status
            transaction.status = notification['new_status']
            transaction.save()

            log_cef('Transaction change success',
                    request,
                    severity=7,
                    cs6Label='old',
                    cs6=STATUSES_INVERTED[old_status],
                    cs7Label='new',
                    cs7=STATUSES_INVERTED[transaction.status])
            log.info('Transaction {0} changed to {1} from {2}'.format(
                transaction.pk, transaction.status, old_status))

        return bundle
Example #6
0
    def clean(self):
        fields = self.fields.keys()
        # If the provider is already set, don't allow it to be changed.
        if self.old.get('provider'):
            del fields[fields.index('provider')]

        keys = set(self.data.keys()).difference(set(fields))
        if keys:
            raise forms.ValidationError('Cannot alter fields: {0}'.format(
                ', '.join(keys)))

        old_text = constants.STATUSES_INVERTED.get(self.old['status'])
        new_text = constants.STATUSES_INVERTED.get(
            self.data.get('status', self.old['status']))

        try:
            check_status(self.old, self.data)
        except forms.ValidationError:
            log_cef('Transaction change failure',
                    self.request,
                    severity=7,
                    cs6Label='old',
                    cs6=old_text,
                    cs7Label='new',
                    cs7=new_text)
            raise

        if new_text != old_text:
            log_cef('Transaction change success',
                    self.request,
                    cs6Label='old',
                    cs6=old_text,
                    cs7Label='new',
                    cs7=new_text)
        return self.cleaned_data
Example #7
0
def reset_confirm_pin(request):
    form = PinForm(data=request.DATA)
    if form.is_valid():
        buyer = form.cleaned_data['buyer']
        confirmed = False

        if buyer.locked_out:
            log_cef('Attempted access to locked out account: %s' % buyer.uuid,
                    request,
                    severity=1)

        else:
            if buyer.new_pin == form.cleaned_data['pin']:
                buyer.pin = form.cleaned_data['pin']
                buyer.new_pin = None
                buyer.needs_pin_reset = False
                buyer.pin_confirmed = True
                buyer.pin_was_locked_out = False
                buyer.save()
                confirmed = True

        output = ConfirmedSerializer(instance=buyer, confirmed=confirmed)
        return Response(output.data)

    raise FormError(form.errors)
    def obj_create(self, bundle, request, **kwargs):
        form = NotificationForm(request, bundle.data)
        bill_conf_id = form.data.get("billing_config_id")
        log.info(
            "Received notification for billing_config_id %r: "
            "bango_response_code: %r; bango_response_message: %r; "
            "bango_trans_id: %r; bango_token: %r; moz_transaction: %r; "
            "amount: %r; currency: %r"
            % (
                bill_conf_id,
                form.data.get("bango_response_code"),
                form.data.get("bango_response_message"),
                form.data.get("bango_trans_id"),
                form.data.get("bango_token"),
                form.data.get("moz_transaction"),
                form.data.get("amount"),
                form.data.get("currency"),
            )
        )

        if not form.is_valid():
            log.info(u"Notification invalid: %s" % bill_conf_id)
            raise self.form_errors(form)

        trans = form.cleaned_data["moz_transaction"]
        states = {OK: ["completed", STATUS_COMPLETED], CANCEL: ["cancelled", STATUS_CANCELLED]}
        message, state = states.get(form.cleaned_data["bango_response_code"], ["failed", STATUS_FAILED])

        log.info(u"Transaction %s: %s" % (message, trans.uuid))
        statsd.incr("bango.notification.%s" % message)
        statsd.decr("solitude.pending_transactions")

        log_cef(
            "Transaction change success",
            request,
            severity=7,
            cs6Label="old",
            cs6=STATUSES_INVERTED.get(trans.status),
            cs7Label="new",
            cs7=STATUSES_INVERTED.get(state),
        )

        trans.status = state
        # This is the id for the actual transaction, useful for refunds.
        trans.uid_support = form.cleaned_data["bango_trans_id"]
        # The price/currency may be empty for error notifications.
        trans.amount = form.cleaned_data["amount"]
        trans.currency = form.cleaned_data["currency"]
        # Set carrier and region.
        if form.cleaned_data.get("network"):
            trans.carrier = form.cleaned_data["carrier"]
            trans.region = form.cleaned_data["region"]

        trans.save()
        return bundle
Example #9
0
    def obj_create(self, bundle, request, **kwargs):
        form = NotificationForm(request, bundle.data)
        bill_conf_id = form.data.get('billing_config_id')
        log.info(
            'Received notification for billing_config_id %r: '
            'bango_response_code: %r; bango_response_message: %r; '
            'bango_trans_id: %r; bango_token: %r; moz_transaction: %r; '
            'amount: %r; currency: %r' %
            (bill_conf_id, form.data.get('bango_response_code'),
             form.data.get('bango_response_message'),
             form.data.get('bango_trans_id'), form.data.get('bango_token'),
             form.data.get('moz_transaction'), form.data.get('amount'),
             form.data.get('currency')))

        if not form.is_valid():
            log.info(u'Notification invalid: %s' % bill_conf_id)
            raise self.form_errors(form)

        trans = form.cleaned_data['moz_transaction']
        states = {
            OK: ['completed', STATUS_COMPLETED],
            CANCEL: ['cancelled', STATUS_CANCELLED]
        }
        message, state = states.get(form.cleaned_data['bango_response_code'],
                                    ['failed', STATUS_FAILED])

        log.info(u'Transaction %s: %s' % (message, trans.uuid))
        statsd.incr('bango.notification.%s' % message)
        statsd.decr('solitude.pending_transactions')

        log_cef('Transaction change success',
                request,
                severity=7,
                cs6Label='old',
                cs6=STATUSES_INVERTED.get(trans.status),
                cs7Label='new',
                cs7=STATUSES_INVERTED.get(state))

        trans.status = state
        # This is the id for the actual transaction, useful for refunds.
        trans.uid_support = form.cleaned_data['bango_trans_id']
        # The price/currency may be empty for error notifications.
        trans.amount = form.cleaned_data['amount']
        trans.currency = form.cleaned_data['currency']
        # Set carrier and region.
        if form.cleaned_data.get('network'):
            trans.carrier = form.cleaned_data['carrier']
            trans.region = form.cleaned_data['region']

        trans.save()
        return bundle
Example #10
0
def notification(request):
    view = BangoResource()
    form = NotificationForm(request, request.DATA)

    bill_conf_id = form.data.get('billing_config_id')
    log.info('Received notification for billing_config_id %r: '
             'bango_response_code: %r; bango_response_message: %r; '
             'bango_trans_id: %r; bango_token: %r; moz_transaction: %r; '
             'amount: %r; currency: %r'
             % (bill_conf_id,
                form.data.get('bango_response_code'),
                form.data.get('bango_response_message'),
                form.data.get('bango_trans_id'),
                form.data.get('bango_token'),
                form.data.get('moz_transaction'),
                form.data.get('amount'),
                form.data.get('currency')))

    if not form.is_valid():
        log.info(u'Notification invalid: %s' % bill_conf_id)
        return view.form_errors(form)

    trans = form.cleaned_data['moz_transaction']
    states = {OK: ['completed', STATUS_COMPLETED],
              CANCEL: ['cancelled', STATUS_CANCELLED]}
    message, state = states.get(form.cleaned_data['bango_response_code'],
                                ['failed', STATUS_FAILED])

    log.info(u'Transaction %s: %s' % (message, trans.uuid))
    statsd.incr('bango.notification.%s' % message)
    statsd.decr('solitude.pending_transactions')

    log_cef('Transaction change success', request, severity=7,
            cs6Label='old', cs6=STATUSES_INVERTED.get(trans.status),
            cs7Label='new', cs7=STATUSES_INVERTED.get(state))

    trans.status = state
    # This is the id for the actual transaction, useful for refunds.
    trans.uid_support = form.cleaned_data['bango_trans_id']
    # The price/currency may be empty for error notifications.
    trans.amount = form.cleaned_data['amount']
    trans.currency = form.cleaned_data['currency']
    # Set carrier and region.
    if form.cleaned_data.get('network'):
        trans.carrier = form.cleaned_data['carrier']
        trans.region = form.cleaned_data['region']

    trans.save()
    return Response(status=204)
Example #11
0
    def _check_for_tampering(self, tok, cleaned_data):
        """
        Use the token service to see if any data has been tampered with.
        """
        cli = get_client().client('token_checker')
        with statsd.timer('solitude.bango.request.checktoken'):
            true_data = cli.service.CheckToken(token=tok)
        if true_data.ResponseCode is None:
            # Any None field means the token was invalid.
            # This might happen if someone tampered with Token= itself in the
            # query string or if Bango's server was messed up.
            statsd.incr('solitude.bango.response.checktoken_fail')
            msg = 'Invalid Bango token: {0}'.format(tok)
            log.error(msg)
            raise forms.ValidationError(msg)

        for form_fld, true_attr in (
            ('moz_signature', 'Signature'),
            ('moz_transaction', 'MerchantTransactionId'),
            ('bango_response_code', 'ResponseCode'),
            ('bango_response_message', 'ResponseMessage'),
            ('bango_trans_id', 'BangoTransactionId'),
        ):
            true_val = getattr(true_data, true_attr)
            # Make sure the true value is a str() just like it is on the query
            # string.
            true_val = str(true_val)
            form_val = cleaned_data.get(form_fld)
            # Since moz_transaction is an object, get the real value.
            if form_val and form_fld == 'moz_transaction':
                form_val = form_val.uuid

            if form_val and form_val != true_val:
                msg = ('Bango query string tampered with: field: {field}; '
                       'fake: {fake}; true: {true}'.format(field=form_fld,
                                                           fake=form_val,
                                                           true=true_val))
                log_cef(msg, self._request, severity=3)
                log.info(msg)
                log.info('token check response: {true_data}'.format(
                    true_data=true_data))
                # Completely reject the form since it was tampered with.
                raise forms.ValidationError(
                    'Form field {0} has been tampered with. '
                    'True: {1}; fake: {2}'.format(form_fld, true_val,
                                                  form_val))
Example #12
0
 def obj_create(self, bundle, request=None, **kwargs):
     buyer = self.get_data(bundle)
     pin = bundle.data.pop('pin')
     if buyer.locked_out:
         log_cef('Attempted access to locked out account: %s'
                 % buyer.uuid, request, severity=1)
         bundle.obj.confirmed = False
         return bundle
     if buyer.new_pin == pin:
         buyer.pin = pin
         buyer.new_pin = None
         buyer.needs_pin_reset = False
         buyer.pin_confirmed = True
         buyer.save()
         bundle.obj.confirmed = True
     else:
         bundle.obj.confirmed = False
     return bundle
Example #13
0
    def _check_for_tampering(self, tok, cleaned_data):
        """
        Use the token service to see if any data has been tampered with.
        """
        cli = get_client().client('token_checker')
        with statsd.timer('solitude.bango.request.checktoken'):
            true_data = cli.service.CheckToken(token=tok)
        if true_data.ResponseCode is None:
            # Any None field means the token was invalid.
            # This might happen if someone tampered with Token= itself in the
            # query string or if Bango's server was messed up.
            statsd.incr('solitude.bango.response.checktoken_fail')
            msg = 'Invalid Bango token: {0}'.format(tok)
            log.error(msg)
            raise forms.ValidationError(msg)

        for form_fld, true_attr in (
                ('moz_signature', 'Signature'),
                ('moz_transaction', 'MerchantTransactionId'),
                ('bango_response_code', 'ResponseCode'),
                ('bango_response_message', 'ResponseMessage'),
                ('bango_trans_id', 'BangoTransactionId'),):
            true_val = getattr(true_data, true_attr)
            # Make sure the true value is a str() just like it is on the query
            # string.
            true_val = str(true_val)
            form_val = cleaned_data.get(form_fld)
            # Since moz_transaction is an object, get the real value.
            if form_val and form_fld == 'moz_transaction':
                form_val = form_val.uuid

            if form_val and form_val != true_val:
                msg = ('Bango query string tampered with: field: {field}; '
                       'fake: {fake}; true: {true}'
                       .format(field=form_fld, fake=form_val, true=true_val))
                log_cef(msg, self._request, severity=3)
                log.info(msg)
                log.info('token check response: {true_data}'
                         .format(true_data=true_data))
                # Completely reject the form since it was tampered with.
                raise forms.ValidationError(
                    'Form field {0} has been tampered with. '
                    'True: {1}; fake: {2}'.format(
                        form_fld, true_val, form_val))
Example #14
0
class Event(viewsets.ViewSet, BaseAPIView):
    """
    Process a Boku server to server notification.

    See Boku Technical Documentation for an example of the data being
    sent in.
    """

    def create(self, request):
        form = EventForm(request.DATA)
        param = form.data.get('param')

        if not form.is_valid():
            log.info('Notification invalid: {0}'.format(param))
            return self.form_errors([form])

        cleaned = form.cleaned_data
        transaction = cleaned['param']

        # Verify this against Boku, this will raise errors if there's
        # an issue.
        log.info('Verifying notification for Boku transaction id: {0}'
                 .format(transaction))

        status = STATUS_COMPLETED
        try:
            verify(cleaned['trx_id'], cleaned['amount'], cleaned['currency'])
        except BokuException, exc:
            log.info('Got non-zero Boku API response: '
                     '{exc}; code={exc.result_code}; msg={exc.result_msg}'
                     .format(exc=exc))
            # Boku will return non-zero error codes to indicate certain
            # transaction states. These states mean that the notification
            # itself is valid.
            if exc.result_code in TRANS_STATUS_FROM_VERIFY_CODE:
                status = TRANS_STATUS_FROM_VERIFY_CODE[exc.result_code]
                log.info('got Boku transaction status {s} from code {c}'
                         .format(s=status, c=exc.result_code))
            else:
                raise

        old_status = transaction.status
        transaction.status = status
        transaction.amount = cleaned['amount']
        transaction.currency = cleaned['currency']
        transaction.uid_support = cleaned['trx_id']
        transaction.save()

        log_cef('Transaction change success', request, severity=7,
                cs6Label='old', cs6=STATUSES_INVERTED[old_status],
                cs7Label='new', cs7=STATUSES_INVERTED[transaction.status])
        log.info('Transaction {0} changed to {1} from {2}'
                 .format(transaction.pk, transaction.status,
                         old_status))
        return HttpResponse(200, '')
Example #15
0
 def obj_create(self, bundle, request=None, **kwargs):
     buyer = self.get_data(bundle)
     if buyer.pin_confirmed:
         if buyer.locked_out:
             log_cef('Attempted access to locked out account: %s'
                     % buyer.uuid, request, severity=1)
             bundle.obj.locked = True
             return bundle
         bundle.obj.valid = buyer.pin == bundle.data.pop('pin')
         if not bundle.obj.valid:
             locked = buyer.incr_lockout()
             if locked:
                 bundle.obj.locked = True
                 log_cef('Locked out account: %s' % buyer.uuid,
                         request, severity=1)
         if bundle.obj.valid and buyer.pin_failures:
             buyer.clear_lockout()
     else:
         bundle.obj.valid = False
     return bundle
Example #16
0
 def obj_create(self, bundle, request=None, **kwargs):
     buyer = self.get_data(bundle)
     if buyer.pin_confirmed:
         if buyer.locked_out:
             log_cef('Attempted access to locked out account: %s'
                     % buyer.uuid, request, severity=1)
             bundle.obj.locked = True
             return bundle
         bundle.obj.valid = buyer.pin == bundle.data.pop('pin')
         if not bundle.obj.valid:
             locked = buyer.incr_lockout()
             if locked:
                 bundle.obj.locked = True
                 log_cef('Locked out account: %s' % buyer.uuid,
                         request, severity=1)
         if bundle.obj.valid and buyer.pin_failures:
             buyer.clear_lockout()
     else:
         bundle.obj.valid = False
     return bundle
Example #17
0
def event(request):
    view = BangoResource()
    form = EventForm(request.DATA, request_encoding=request.encoding)
    if not form.is_valid():
        log.info('Event invalid.')
        return view.form_errors(form)

    notification = form.cleaned_data['notification']
    transaction = form.cleaned_data['transaction']

    if notification['new_status'] != transaction.status:
        old_status = transaction.status
        transaction.status = notification['new_status']
        transaction.save()

        log_cef('Transaction change success', request, severity=7,
                cs6Label='old', cs6=STATUSES_INVERTED[old_status],
                cs7Label='new', cs7=STATUSES_INVERTED[transaction.status])
        log.info('Transaction {0} changed to {1} from {2}'
                 .format(transaction.pk, transaction.status,
                         old_status))

    return Response(status=204)
Example #18
0
def reset_confirm_pin(request):
    form = PinForm(data=request.DATA)
    if form.is_valid():
        buyer = form.cleaned_data['buyer']
        confirmed = False

        if buyer.locked_out:
            log_cef('Attempted access to locked out account: %s'
                    % buyer.uuid, request, severity=1)

        else:
            if buyer.new_pin == form.cleaned_data['pin']:
                buyer.pin = form.cleaned_data['pin']
                buyer.new_pin = None
                buyer.needs_pin_reset = False
                buyer.pin_confirmed = True
                buyer.pin_was_locked_out = False
                buyer.save()
                confirmed = True

        output = ConfirmedSerializer(instance=buyer, confirmed=confirmed)
        return Response(output.data)

    raise FormError(form.errors)
Example #19
0
def verify_pin(request):
    form = PinForm(data=request.DATA)

    if form.is_valid():
        buyer = form.cleaned_data['buyer']
        valid = False
        locked = False

        if buyer.pin_confirmed:
            # Note that the incr_lockout and clear_lockout methods
            # trigger saves on the object. You should not do a save
            # in this view as well for fear of stomping on the save
            # caused by those methods.
            if buyer.locked_out:
                log_cef('Attempted access to locked out account: %s' %
                        buyer.uuid,
                        request,
                        severity=1)
                locked = True

            else:
                valid = buyer.pin == form.cleaned_data['pin']
                if not valid:
                    locked = buyer.incr_lockout()
                    if locked:
                        locked = True
                        log_cef('Locked out account: %s' % buyer.uuid,
                                request,
                                severity=1)
                else:
                    buyer.clear_lockout(clear_was_locked=True)

        output = VerifiedSerializer(instance=buyer, valid=valid, locked=locked)
        return Response(output.data)

    raise FormError(form.errors)
Example #20
0
def error_500(request):
    exception = sys.exc_info()[1]
    log_cef(str(exception), request, severity=3)
    return response(request, status=500,
                    data={'error': exception.__class__.__name__})