Пример #1
0
from datetime import datetime, timedelta
from optparse import make_option

from django.core.management.base import BaseCommand, CommandError

import braintree
import requests
from braintree.util.crypto import Crypto

from lib.brains.management.commands.samples import webhooks
from lib.brains.models import BraintreeSubscription
from lib.transactions.models import Transaction
from payments_config import products
from solitude.logger import getLogger

log = getLogger('s.brains.management')
valid_kinds = [
    'subscription_charged_successfully',
    'subscription_charged_unsuccessfully',
    'subscription_canceled',
]


class Command(BaseCommand):

    """
    This is a crude way to generate and test webhook notifications.

    It does this by grabbing a sample request from Braintree and then
    reformatting it with some local data. There are some inherent problems
    with this:
Пример #2
0
from django.shortcuts import get_object_or_404

from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.buyers.forms import PinForm
from lib.buyers.models import Buyer
from lib.buyers.serializers import (BuyerSerializer, ConfirmedSerializer,
                                    VerifiedSerializer)
from solitude.base import log_cef, NonDeleteModelViewSet
from solitude.errors import FormError
from solitude.logger import getLogger

log = getLogger('s.buyer')


class BuyerViewSet(NonDeleteModelViewSet):
    queryset = Buyer.objects.all()
    serializer_class = BuyerSerializer
    filter_fields = ('uuid', 'active')


@api_view(['POST'])
def confirm_pin(request):
    form = PinForm(data=request.DATA)

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

        if buyer.pin == form.cleaned_data['pin']:
Пример #3
0
import os
import sys
from optparse import make_option

from django.conf import settings
from django.core.management.base import BaseCommand

import boto
from boto.s3.key import Key
from solitude.logger import getLogger

log = getLogger('s.s3')


def push(source):
    if not all(settings.S3_AUTH.values() + [
            settings.S3_BUCKET,
    ]):
        print 'Settings incomplete, cannot push to S3.'
        sys.exit(1)

    dest = os.path.basename(source)
    conn = boto.connect_s3(settings.S3_AUTH['key'], settings.S3_AUTH['secret'])
    bucket = conn.get_bucket(settings.S3_BUCKET)
    k = Key(bucket)
    k.key = dest
    k.set_contents_from_filename(source)
    log.debug('Uploaded: {0} to: {1}'.format(source, dest))


class Command(BaseCommand):
Пример #4
0
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.views import debug

import requests
from aesfield.field import AESField
from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.bango.constants import STATUS_BAD
from lib.sellers.models import Seller, SellerProduct
from lib.transactions.constants import STATUS_FAILED
from solitude.logger import getLogger

log = getLogger("s.services")


class StatusObject(object):
    def __init__(self):
        self.status = {}
        self.error = None

    @property
    def is_proxy(self):
        return getattr(settings, "SOLITUDE_PROXY", {})

    def test_cache(self):
        # caching fails silently so we have to read from it after writing.
        cache.set("status", "works")
        if cache.get("status") == "works":
Пример #5
0
import requests
from curling.lib import sign_request
from django_statsd.clients import statsd
from lxml import etree
from slumber import url_join

from lib.bango.constants import HEADERS_SERVICE_GET, HEADERS_WHITELIST_INVERTED
from lib.boku.client import get_boku_request_signature
from lib.paypal.client import get_client as paypal_client
from lib.paypal.constants import HEADERS_URL_GET, HEADERS_TOKEN_GET
from lib.paypal.map import urls
from solitude.base import dump_request, dump_response
from solitude.logger import getLogger

log = getLogger('s.proxy')
bango_timeout = getattr(settings, 'BANGO_TIMEOUT', 10)


def qs_join(**kwargs):
    return '{url}?{query}'.format(**kwargs)


class Proxy(object):
    # Override this in your proxy class.
    name = None
    # Values that we'll populate from the request, optionally.
    body = None
    headers = None
    url = None
    # Name of settings variables.
Пример #6
0
from datetime import datetime, timedelta

from django import forms
from django.conf import settings

from django_paranoia.forms import ParanoidForm

from lib.transactions import constants
from lib.transactions.constants import STATUSES
from solitude.base import log_cef
from solitude.logger import getLogger

log = getLogger('s.transaction')


def check_status(old, new):
    if ((old['created'] + timedelta(seconds=settings.TRANSACTION_LOCKDOWN)) <
            datetime.now()):
        raise forms.ValidationError('Transaction locked down')

    elif old['status'] == constants.STATUS_PENDING:
        return

    elif old['status'] in [
            constants.STATUS_FAILED, constants.STATUS_ERRORED,
            constants.STATUS_CANCELLED
    ]:
        msg = 'Cannot change state: {0}'.format(old['status'])
        log.error(msg)
        raise forms.ValidationError(msg)
Пример #7
0
import csv
import os
import tempfile
from datetime import datetime, timedelta
from optparse import make_option

from lib.transactions import constants
from lib.transactions.models import Transaction
from solitude.logger import getLogger
from solitude.management.commands.push_s3 import push

from django.core.management.base import BaseCommand, CommandError

log = getLogger('s.transactions')


def generate_log(day, filename, log_type):
    out = open(filename, 'w')
    writer = csv.writer(out)
    next_day = day + timedelta(days=1)
    writer.writerow(('version', 'uuid', 'created', 'modified', 'amount',
                     'currency', 'status', 'buyer', 'seller', 'source',
                     'carrier', 'region', 'provider'))

    transactions = Transaction.objects.filter(modified__range=(day, next_day))

    if log_type == 'stats':
        for row in transactions:
            row.log.get_or_create(type=constants.LOG_STATS)
            writer.writerow(row.for_log())
Пример #8
0
from django.http import Http404
from django.test.client import Client
from django.utils.decorators import method_decorator
from django.views.decorators.http import etag

from cef import log_cef as _log_cef
from rest_framework import mixins
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.utils.encoders import JSONEncoder
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet

from solitude.logger import getLogger

log = getLogger('s')
dump_log = getLogger('s.dump')
sys_cef_log = getLogger('s.cef')


def get_objects(data):
    # If its a Serializer.
    if isinstance(data, BaseSerializer):
        return [data.object]

    # If its a queryset.
    if isinstance(data, QuerySet):
        return data


def etag_func(request, data, *args, **kwargs):
Пример #9
0
from tastypie.authorization import Authorization
from tastypie.exceptions import ImmediateHttpResponse, InvalidFilterError
from tastypie.fields import ToOneField
from tastypie.resources import (ModelResource as TastyPieModelResource,
                                Resource as TastyPieResource)
from tastypie.utils import dict_strip_unicode_keys
from tastypie.validation import FormValidation
import test_utils

from lib.delayable.tasks import delayable

from solitude.authentication import OAuthAuthentication
from solitude.logger import getLogger


log = getLogger('s')
tasty_log = getLogger('django.request.tastypie')


def colorize(colorname, text):
    if curlish:
        return get_color(colorname) + text + ANSI_CODES['reset']
    return text


def formatted_json(json):
    if curlish:
        print_formatted_json(json)
        return
    print json
Пример #10
0
from django.db.transaction import set_rollback

from rest_framework.response import Response
from rest_framework.views import exception_handler

from lib.bango.errors import BangoImmediateError
from solitude.logger import getLogger

log = getLogger('s')


def custom_exception_handler(exc):
    # If you raise an error in solitude, it comes to here and
    # we rollback the transaction.
    log.info('Handling exception, about to roll back for: {}, {}'.format(
        type(exc), exc.message))
    set_rollback(True)

    if hasattr(exc, 'formatter'):
        try:
            return Response(exc.formatter(exc).format(),
                            status=getattr(exc, 'status_code', 422))
        except:
            # If the formatter fails, fall back to the standard
            # error formatting.
            log.exception('Failed to use formatter.')

    if isinstance(exc, BangoImmediateError):
        return Response(exc.message, status=400)

    return exception_handler(exc)
Пример #11
0
from tastypie import http
from tastypie.authorization import Authorization
from tastypie.exceptions import ImmediateHttpResponse, InvalidFilterError
from tastypie.fields import ToOneField
from tastypie.resources import (ModelResource as TastyPieModelResource,
                                Resource as TastyPieResource,
                                convert_post_to_patch)
from tastypie.utils import dict_strip_unicode_keys
from tastypie.validation import FormValidation
import test_utils

from solitude.authentication import OAuthAuthentication
from solitude.logger import getLogger


log = getLogger('s')
sys_cef_log = getLogger('s.cef')
tasty_log = getLogger('django.request.tastypie')


def etag_func(request, data, *args, **kwargs):
    if hasattr(request, 'initial_etag'):
        all_etags = [str(request.initial_etag)]
    else:
        if data:
            try:
                objects = [data.obj]  # Detail case.
            except AttributeError:
                try:
                    objects = data['objects']  # List case.
                except (TypeError, KeyError):
Пример #12
0
from django.shortcuts import get_object_or_404

from rest_framework import viewsets
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response

from lib.provider.serializers import SellerProductReferenceSerializer, SellerReferenceSerializer, TermsSerializer
from lib.provider.views import ProxyView
from lib.sellers.models import SellerProductReference, SellerReference
from solitude.logger import getLogger

log = getLogger("s.provider")


class MashupView(ProxyView):
    """
    Overrides the normal proxy view to first process the data locally
    and then remotely, storing data in reference_id fields on the
    objects.

    This allows clients interacting with solitude to make one call which
    hits solitude and the back end server, limiting the amount of knowledge
    the client has to have about the backend service, such as zippy.
    """

    _proxy_reset = False

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.DATA)

        if serializer.is_valid():
Пример #13
0
from django.conf import settings

from .client import get_client
from .errors import PaypalError

from solitude.logger import getLogger

log = getLogger('s.paypal')


class Check(object):
    """
    Run a series of tests on PayPal for either an addon or a paypal_id.
    The add-on is not required, but we'll do another check or two if the
    add-on is there.
    """

    def __init__(self, paypal_id=None, token=None, prices=None):
        # If this state flips to False, it means they need to
        # go to Paypal and re-set up permissions. We'll assume the best.
        self.state = {'permissions': True}
        self.tests = ['id', 'refund', 'currencies']
        for test in self.tests:
            # Three states for pass:
            #   None: haven't tried
            #   False: tried but failed
            #   True: tried and passed
            self.state[test] = {'pass': None, 'errors': []}
        self.paypal_id = paypal_id
        self.paypal_permissions_token = token
        self.prices = prices
Пример #14
0
from rest_framework.decorators import api_view
from rest_framework.request import Request
from rest_framework.response import Response

from lib.buyers.field import ConsistentSigField
from lib.buyers.forms import PinForm
from lib.buyers.models import Buyer
from lib.buyers.serializers import (
    BuyerSerializer, ConfirmedSerializer, VerifiedSerializer)
from solitude.base import log_cef, NonDeleteModelViewSet
from solitude.errors import FormError
from solitude.filter import StrictQueryFilter
from solitude.logger import getLogger

log = getLogger('s.buyer')


class HashedEmailRequest(Request):

    @property
    def QUERY_PARAMS(self):
        data = self._request.GET.copy()
        if 'email' in data:
            email = data.pop('email')
            if len(email) > 1:
                raise ValueError('Multiple values of email not supported')
            data['email_sig'] = ConsistentSigField()._hash(email[0])
        return data

Пример #15
0
# Turn the method into the approiate name. If the Bango WSDL diverges this will
# need to change.
def get_request(name):
    return name + 'Request'


def get_response(name):
    return name + 'Response'


def get_result(name):
    return name + 'Result'


log = getLogger('s.bango')


class Client(object):
    def __getattr__(self, attr):
        for name, methods in (['exporter', exporter], ['billing', billing],
                              ['direct',
                               direct], ['token_checker', token_checker]):
            if attr in methods:
                return functools.partial(self.call, attr, wsdl=str(name))
        raise AttributeError('Unknown request: %s' % attr)

    def call(self, name, data, wsdl='exporter'):
        log.info('Bango client call: {0} from wsdl: {1}'.format(name, wsdl))
        client = self.client(wsdl)
        package = client.factory.create(get_request(name))
Пример #16
0
from rest_framework.response import Response

from lib.bango.client import ClientMock
from lib.bango.constants import CANT_REFUND, NOT_SUPPORTED, OK, PENDING
from lib.bango.errors import BangoAnticipatedError
from lib.bango.forms import RefundForm, RefundStatusForm
from lib.bango.serializers import EasyObject, RefundSerializer
from lib.bango.views.base import BangoResource
from lib.transactions.constants import (STATUS_COMPLETED, STATUS_FAILED,
                                        STATUS_PENDING, TYPE_REFUND,
                                        TYPE_REFUND_MANUAL)
from lib.transactions.models import Transaction
from solitude.base import NonDeleteModelViewSet
from solitude.logger import getLogger

log = getLogger('s.bango.refund')


class RefundViewSet(NonDeleteModelViewSet, BangoResource):
    """
    A specific resource for creating refunds and then checking the state of
    that refund against Bango. Since a transaction is created, you can examine
    the state of the transaction in solitude without having to check against
    Bango.
    """

    serializer_class = RefundSerializer
    queryset = Transaction.objects.filter()

    def update(self):
        return Response(status=405)
Пример #17
0
from urlparse import urljoin

from django.conf import settings
import oauth2

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from tastypie.authentication import Authentication

from solitude.logger import getLogger
from solitude.middleware import set_oauth_key

log = getLogger('s.auth')


class Consumer(object):

    def __init__(self, key, secret=None):
        self.key = key
        self.secret = secret or settings.CLIENT_OAUTH_KEYS[key]


class OAuthError(RuntimeError):
    def __init__(self, message='OAuth error occured.'):
        self.message = message


class OAuthAuthentication(Authentication):
    """
    This is based on https://github.com/amrox/django-tastypie-two-legged-oauth
    with permission.
Пример #18
0
from tastypie.fields import ToOneField
from tastypie.resources import (
    ModelResource as TastyPieModelResource,
    Resource as TastyPieResource,
    convert_post_to_patch,
)
from tastypie.utils import dict_strip_unicode_keys
from tastypie.validation import FormValidation
import test_utils

from solitude.authentication import OAuthAuthentication
from solitude.logger import getLogger
from solitude.related_fields import PathRelatedField


log = getLogger("s")
dump_log = getLogger("s.dump")
sys_cef_log = getLogger("s.cef")
tasty_log = getLogger("django.request.tastypie")


def etag_func(request, data, *args, **kwargs):
    if hasattr(request, "initial_etag"):
        all_etags = [str(request.initial_etag)]
    else:
        if data:
            try:
                objects = [data.obj]  # Detail case.
            except AttributeError:
                try:
                    objects = data["objects"]  # List case.
Пример #19
0
import os
import sys
from optparse import make_option

from django.conf import settings
from django.core.management.base import BaseCommand

import boto
from boto.s3.key import Key

from solitude.logger import getLogger

log = getLogger('s.s3')


def push(source):
    if not all(settings.S3_AUTH.values() + [settings.S3_BUCKET,]):
        print 'Settings incomplete, cannot push to S3.'
        sys.exit(1)

    dest = os.path.basename(source)
    conn = boto.connect_s3(settings.S3_AUTH['key'],
                           settings.S3_AUTH['secret'])
    bucket = conn.get_bucket(settings.S3_BUCKET)
    k = Key(bucket)
    k.key = dest
    k.set_contents_from_filename(source)
    log.debug('Uploaded: {0} to: {1}'.format(source, dest))


class Command(BaseCommand):
Пример #20
0
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.views import debug

import requests
from aesfield.field import AESField
from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.bango.constants import STATUS_BAD
from lib.sellers.models import Seller, SellerProduct
from lib.transactions.constants import STATUS_FAILED
from solitude.logger import getLogger

log = getLogger('s.services')


class StatusObject(object):

    def __init__(self):
        self.status = {}
        self.error = None

    @property
    def is_proxy(self):
        return getattr(settings, 'SOLITUDE_PROXY', {})

    def test_cache(self):
        # caching fails silently so we have to read from it after writing.
        cache.set('status', 'works')
Пример #21
0
from datetime import datetime, timedelta
from optparse import make_option

from django.core.management.base import BaseCommand, CommandError

import braintree
import requests
from braintree.util.crypto import Crypto

from lib.brains.management.commands.samples import webhooks
from lib.brains.models import BraintreeSubscription
from lib.transactions.models import Transaction
from payments_config import products
from solitude.logger import getLogger

log = getLogger('s.brains.management')
valid_kinds = [
    'subscription_charged_successfully',
    'subscription_charged_unsuccessfully',
    'subscription_canceled',
]


class Command(BaseCommand):
    """
    This is a crude way to generate and test webhook notifications.

    It does this by grabbing a sample request from Braintree and then
    reformatting it with some local data. There are some inherent problems
    with this:
    * the XML format might change
Пример #22
0
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse

import requests
from django_statsd.clients import statsd
from lxml import etree
from slumber import url_join

from curling.lib import sign_request
from lib.bango.constants import HEADERS_ALLOWED_INVERTED, HEADERS_SERVICE_GET
from lib.proxy.constants import HEADERS_URL_GET
from solitude.base import dump_request, dump_response
from solitude.logger import getLogger

log = getLogger('s.proxy')
bango_timeout = getattr(settings, 'BANGO_TIMEOUT', 10)


def qs_join(**kwargs):
    return '{url}?{query}'.format(**kwargs)


class Proxy(object):
    # Override this in your proxy class.
    name = None
    # Values that we'll populate from the request, optionally.
    body = None
    headers = None
    url = None
    # Name of settings variables.
Пример #23
0
from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.brains import serializers
from lib.brains.client import get_client
from lib.brains.errors import BraintreeResultError
from lib.brains.forms import PaymentMethodForm, PayMethodDeleteForm
from lib.brains.models import BraintreePaymentMethod
from solitude.base import NoAddModelViewSet
from solitude.constants import PAYMENT_METHOD_CARD
from solitude.errors import FormError
from solitude.logger import getLogger

log = getLogger('s.brains')


@api_view(['POST'])
def delete(request):
    form = PayMethodDeleteForm(request.DATA)

    if not form.is_valid():
        raise FormError(form.errors)

    solitude_method = form.cleaned_data['paymethod']
    solitude_method.braintree_delete()
    solitude_method.active = False
    solitude_method.save()

    log.info('Payment method deleted from braintree: {}'
             .format(solitude_method.pk))
Пример #24
0
from urlparse import urljoin

from django.conf import settings
import oauth2

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from tastypie.authentication import Authentication

from solitude.logger import getLogger
from solitude.middleware import set_oauth_key

log = getLogger('s.auth')


class Consumer(object):
    def __init__(self, key, secret=None):
        self.key = key
        self.secret = secret or settings.CLIENT_OAUTH_KEYS[key]


class OAuthError(RuntimeError):
    def __init__(self, message='OAuth error occured.'):
        self.message = message


class OAuthAuthentication(Authentication):
    """
    This is based on https://github.com/amrox/django-tastypie-two-legged-oauth
    with permission.
    """
Пример #25
0
from django.conf import settings

from .client import get_client
from .errors import PaypalError

from solitude.logger import getLogger

log = getLogger('s.paypal')


class Check(object):
    """
    Run a series of tests on PayPal for either an addon or a paypal_id.
    The add-on is not required, but we'll do another check or two if the
    add-on is there.
    """
    def __init__(self, paypal_id=None, token=None, prices=None):
        # If this state flips to False, it means they need to
        # go to Paypal and re-set up permissions. We'll assume the best.
        self.state = {'permissions': True}
        self.tests = ['id', 'refund', 'currencies']
        for test in self.tests:
            # Three states for pass:
            #   None: haven't tried
            #   False: tried but failed
            #   True: tried and passed
            self.state[test] = {'pass': None, 'errors': []}
        self.paypal_id = paypal_id
        self.paypal_permissions_token = token
        self.prices = prices
        self.paypal = get_client()
Пример #26
0
import base64

from braintree.util.xml_util import XmlUtil
from braintree.webhook_notification import WebhookNotification
from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.brains.client import get_client
from lib.brains.forms import WebhookParseForm, WebhookVerifyForm
from lib.brains.webhooks import Processor
from solitude.errors import FormError
from solitude.logger import getLogger

log = getLogger('s.brains')
debug_log = getLogger('s.webhook')


def webhook(request):
    if request.method.lower() == 'get':
        return verify(request)
    return parse(request)


@api_view(['POST'])
def parse(request):
    form = WebhookParseForm(request.DATA)
    if not form.is_valid():
        raise FormError(form.errors)

    # Parse the gateway without doing a validation on this server.
    # The validation has happened on the solitude-auth server.
Пример #27
0
import uuid
from datetime import datetime

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.dispatch import Signal

from aesfield.field import AESField

from .field import ConsistentSigField, HashField
from solitude.base import Model
from solitude.logger import getLogger

log = getLogger(__name__)
ANONYMISED = 'anonymised-uuid:'


class Buyer(Model):
    uuid = models.CharField(max_length=255, db_index=True, unique=True)
    pin = HashField(blank=True, null=True)
    pin_confirmed = models.BooleanField(default=False)
    pin_failures = models.IntegerField(default=0)
    pin_locked_out = models.DateTimeField(blank=True, null=True)
    pin_was_locked_out = models.BooleanField(default=False)
    active = models.BooleanField(default=True, db_index=True)
    new_pin = HashField(blank=True, null=True)
    needs_pin_reset = models.BooleanField(default=False)
    email = AESField(blank=True, null=True, aes_key='buyeremail:key')
    # Because the email field is encrypted we can't do lookups in mysql on it.
    # This allows us to use a field for lookups, without exposing anything in
Пример #28
0
from django.db.transaction import set_rollback

from rest_framework.response import Response
from rest_framework.views import exception_handler

from lib.bango.errors import BangoImmediateError
from solitude.logger import getLogger

log = getLogger('s')


def custom_exception_handler(exc):
    # If you raise an error in solitude, it comes to here and
    # we rollback the transaction.
    log.info('Handling exception, about to roll back for: {}, {}'
             .format(type(exc), exc.message))
    set_rollback(True)

    if hasattr(exc, 'formatter'):
        try:
            return Response(exc.formatter(exc).format(),
                            status=getattr(exc, 'status_code', 422))
        except:
            # If the formatter fails, fall back to the standard
            # error formatting.
            log.exception('Failed to use formatter.')

    if isinstance(exc, BangoImmediateError):
        return Response(exc.message, status=400)
Пример #29
0
from django.db import models
from django.dispatch import receiver

from django_statsd.clients import statsd

from lib.bango.signals import create as bango_create
from lib.paypal.signals import create as paypal_create
from lib.transactions import constants

from solitude.base import get_object_or_404, Model
from solitude.logger import getLogger

log = getLogger('s.transaction')
stats_log = getLogger('s.transaction.stats')


class Transaction(Model):
    # In the case of some transactions (e.g. Bango) we don't know the amount
    # until the transaction reaches a certain stage.
    amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True,
                                 null=True)
    buyer = models.ForeignKey('buyers.Buyer', blank=True, null=True,
                              db_index=True)
    currency = models.CharField(max_length=3, blank=True)
    provider = models.PositiveIntegerField(choices=constants.SOURCES_CHOICES)
    related = models.ForeignKey('self', blank=True, null=True,
                                on_delete=models.PROTECT,
                                related_name='relations')
    seller_product = models.ForeignKey('sellers.SellerProduct', db_index=True)
    status = models.PositiveIntegerField(default=constants.STATUS_DEFAULT,
                                         choices=constants.STATUSES_CHOICES)
Пример #30
0
from rest_framework.filters import DjangoFilterBackend

from solitude.errors import InvalidQueryParams
from solitude.logger import getLogger

log = getLogger('s.filter')


class StrictQueryFilter(DjangoFilterBackend):

    """
    Don't allow people to typo request params and return all the objects.
    Instead limit it down to the parameters allowed in filter_fields.
    """

    def get_filter_class(self, view, queryset=None):
        klass = (super(StrictQueryFilter, self)
                 .get_filter_class(view, queryset=queryset))
        try:
            # If an ordering exists on the model, use that.
            klass._meta.order_by = klass.Meta.model.Meta.ordering
        except AttributeError:
            pass
        return klass

    def filter_queryset(self, request, queryset, view):
        requested = set(request.QUERY_PARAMS.keys())
        allowed = set(getattr(view, 'filter_fields', []))
        difference = requested.difference(allowed)
        if difference:
            raise InvalidQueryParams(
Пример #31
0
import uuid
from datetime import datetime

from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.dispatch import Signal

from aesfield.field import AESField

from .field import HashField
from solitude.base import Model
from solitude.logger import getLogger

log = getLogger(__name__)
ANONYMISED = 'anonymised-uuid:'


class Buyer(Model):
    uuid = models.CharField(max_length=255, db_index=True, unique=True)
    pin = HashField(blank=True, null=True)
    pin_confirmed = models.BooleanField(default=False)
    pin_failures = models.IntegerField(default=0)
    pin_locked_out = models.DateTimeField(blank=True, null=True)
    pin_was_locked_out = models.BooleanField(default=False)
    active = models.BooleanField(default=True, db_index=True)
    new_pin = HashField(blank=True, null=True)
    needs_pin_reset = models.BooleanField(default=False)
    email = AESField(blank=True, null=True, aes_key='buyeremail:key')
    locale = models.CharField(max_length=255, blank=True, null=True)
    # When this is True it means the buyer was created by some trusted
Пример #32
0
from tastypie.http import HttpNotFound

from cached import SimpleResource
from lib.bango.client import ClientMock
from lib.bango.constants import CANT_REFUND, NOT_SUPPORTED, OK, PENDING
from lib.bango.errors import BangoFormError
from lib.bango.forms import RefundForm, RefundStatusForm
from lib.transactions.constants import (STATUS_COMPLETED, STATUS_FAILED,
                                        STATUS_PENDING, TYPE_REFUND)
from lib.transactions.models import Transaction
from lib.transactions.resources import TransactionResource

from solitude.logger import getLogger


log = getLogger('s.bango.refund')


class RefundResponse(object):

    def __init__(self, bango, transaction):
        self.pk = transaction.pk
        self.status = bango
        self.transaction = transaction


class BangoResponse(object):

    def __init__(self, code, message, id):
        self.responseCode = code
        self.responseMessage = message
Пример #33
0
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.views import debug

import requests
from aesfield.field import AESField
from rest_framework.decorators import api_view
from rest_framework.response import Response

from lib.bango.constants import STATUS_BAD
from lib.sellers.models import Seller, SellerProduct
from lib.transactions.constants import STATUS_FAILED
from solitude.logger import getLogger

log = getLogger('s.services')


class StatusObject(object):
    def __init__(self):
        self.status = {}
        self.error = None

    @property
    def is_proxy(self):
        return getattr(settings, 'SOLITUDE_PROXY', {})

    def test_cache(self):
        # caching fails silently so we have to read from it after writing.
        cache.set('status', 'works')
        if cache.get('status') == 'works':
Пример #34
0
from django.http import HttpResponse

from rest_framework import viewsets

from lib.boku.constants import TRANS_STATUS_FROM_VERIFY_CODE
from lib.boku.errors import BokuException
from lib.boku.utils import verify
from lib.boku.forms import EventForm
from lib.transactions.constants import (STATUS_COMPLETED, STATUSES_INVERTED)

from solitude.base import BaseAPIView, log_cef
from solitude.logger import getLogger

log = getLogger('s.boku')


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])
Пример #35
0
from django.http import Http404
from django.test.client import Client
from django.utils.decorators import method_decorator
from django.views.decorators.http import etag

from cef import log_cef as _log_cef
from rest_framework import mixins
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.utils.encoders import JSONEncoder
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet

from solitude.logger import getLogger

log = getLogger('s')
dump_log = getLogger('s.dump')
sys_cef_log = getLogger('s.cef')


def get_objects(data):
    # If its a Serializer.
    if isinstance(data, BaseSerializer):
        return [data.object]

    # If its a queryset.
    if isinstance(data, QuerySet):
        return data


def etag_func(request, data, *args, **kwargs):
Пример #36
0
from collections import defaultdict

from django.core.exceptions import NON_FIELD_ERRORS

from solitude.errors import ErrorFormatter
from solitude.logger import getLogger

log = getLogger('s.brains')


class MockError(Exception):
    """
    An attempt was made to use the mock, without a corresponding entry
    in the mocks dictionary.
    """


class BraintreeFormatter(ErrorFormatter):
    def format(self):
        errors = defaultdict(list)
        for error in self.error.result.errors.deep_errors:
            errors[error.attribute].append({
                'code': error.code,
                'message': error.message
            })

        # If there's not a verification object,
        # there will be a transaction object or neither.
        error = (self.error.result.credit_card_verification
                 or self.error.result.transaction)
        if error:
Пример #37
0
from django_statsd.clients import statsd

from cached import Resource
from lib.bango.constants import CANCEL, OK
from lib.bango.forms import EventForm, NotificationForm
from lib.transactions.constants import (STATUS_CANCELLED, STATUS_COMPLETED,
                                        STATUS_FAILED)

from solitude.logger import getLogger

log = getLogger('s.bango')


class NotificationResource(Resource):
    """
    Process a Bango notification.

    See the success URL endpoint in WebPay for an example of the Bango
    query string.
    """

    class Meta(Resource.Meta):
        resource_name = 'notification'
        list_allowed_methods = ['post']

    def obj_create(self, bundle, request, **kwargs):
        form = NotificationForm(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; moz_transaction: %r; '
Пример #38
0
import time
from collections import OrderedDict

from django.core.urlresolvers import reverse
from django.db import models
from django.dispatch import receiver

from django_statsd.clients import statsd

from lib.transactions import constants
from solitude.base import Model
from solitude.logger import getLogger
from solitude.utils import shorter

log = getLogger('s.transaction')
stats_log = getLogger('s.transaction.stats')


class Transaction(Model):
    # In the case of some transactions (e.g. Bango) we don't know the amount
    # until the transaction reaches a certain stage.
    amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True,
                                 null=True)
    buyer = models.ForeignKey('buyers.Buyer', blank=True, null=True,
                              db_index=True)
    # The carrier if this was carrier billing.
    carrier = models.CharField(max_length=255, blank=True, null=True,
                               db_index=True)
    currency = models.CharField(max_length=3, blank=True)
    provider = models.PositiveIntegerField(
        choices=constants.PROVIDERS_CHOICES, blank=True, null=True)
Пример #39
0
from django_statsd.clients import statsd

from cached import Resource
from lib.bango.constants import CANCEL, OK
from lib.bango.forms import EventForm, NotificationForm
from lib.transactions.constants import STATUS_CANCELLED, STATUS_COMPLETED, STATUS_FAILED, STATUSES_INVERTED

from solitude.base import log_cef
from solitude.logger import getLogger

log = getLogger("s.bango")


class NotificationResource(Resource):
    """
    Process a Bango notification.

    See the success URL endpoint in WebPay for an example of the Bango
    query string.
    """

    class Meta(Resource.Meta):
        resource_name = "notification"
        list_allowed_methods = ["post"]

    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; "
Пример #40
0
from rest_framework.filters import DjangoFilterBackend

from solitude.errors import InvalidQueryParams
from solitude.logger import getLogger

log = getLogger('s.filter')


class StrictQueryFilter(DjangoFilterBackend):
    """
    Don't allow people to typo request params and return all the objects.
    Instead limit it down to the parameters allowed in filter_fields.
    """
    def get_filter_class(self, view, queryset=None):
        klass = (super(StrictQueryFilter,
                       self).get_filter_class(view, queryset=queryset))
        try:
            # If an ordering exists on the model, use that.
            klass._meta.order_by = klass.Meta.model.Meta.ordering
        except AttributeError:
            pass
        return klass

    def filter_queryset(self, request, queryset, view):
        requested = set(request.QUERY_PARAMS.keys())
        allowed = set(getattr(view, 'filter_fields', []))
        difference = requested.difference(allowed)
        if difference:
            raise InvalidQueryParams(detail='Incorrect query parameters: ' +
                                     ','.join(difference))
Пример #41
0
from tastypie import http
from tastypie.authorization import Authorization
from tastypie.exceptions import ImmediateHttpResponse, InvalidFilterError
from tastypie.fields import ToOneField
from tastypie.resources import (ModelResource as TastyPieModelResource,
                                Resource as TastyPieResource,
                                convert_post_to_patch)
from tastypie.utils import dict_strip_unicode_keys
from tastypie.validation import FormValidation
import test_utils

from solitude.authentication import OAuthAuthentication
from solitude.logger import getLogger

log = getLogger('s')
sys_cef_log = getLogger('s.cef')
tasty_log = getLogger('django.request.tastypie')


def etag_func(request, data, *args, **kwargs):
    if hasattr(request, 'initial_etag'):
        all_etags = [str(request.initial_etag)]
    else:
        if data:
            try:
                objects = [data.obj]  # Detail case.
            except AttributeError:
                try:
                    objects = data['objects']  # List case.
                except (TypeError, KeyError):
Пример #42
0
from lib.boku.constants import CURRENCIES
from lib.transactions.constants import (PROVIDER_BOKU, STATUS_COMPLETED)
from lib.transactions.models import Transaction

from django.conf import settings
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _

from lib.boku import constants
from lib.boku.client import get_client
from lib.boku.errors import BokuException
from lib.sellers.models import Seller
from solitude.logger import getLogger

log = getLogger('s.boku')


class BokuForm(forms.Form):
    """
    Boku form fields all have - in them for the field names, which makes them
    hard to process in a Django form. This converts all the - to _.

    It does not check if that causes any conflicts.
    """
    def __init__(self, data=None, files=None, **kwargs):
        data = dict((k.replace('-', '_'), v) for k, v in (data or {}).items())
        super(BokuForm, self).__init__(data=data, files=files, **kwargs)


class EventForm(BokuForm):
Пример #43
0
from collections import defaultdict

from django.conf import settings

from curling.lib import API
from solitude.logger import getLogger

log = getLogger('s.provider')
mock_data = defaultdict(dict)


class Client(object):

    def __init__(self, reference_name):
        self.config = settings.ZIPPY_CONFIGURATION.get(reference_name)
        self.api = None
        if self.config:
            self.api = API(self.config['url'], append_slash=False)
            self.api.activate_oauth(self.config['auth']['key'],
                                    self.config['auth']['secret'],
                                    params={'oauth_token': 'not-implemented'})
        else:
            log.warning('No config for {ref}; oauth disabled'
                        .format(ref=reference_name))


class APIMockObject(object):

    def __init__(self, resource_name, pk=None):
        self.resource_name = resource_name
        self.pk = pk
Пример #44
0
from django.http import HttpResponse

from rest_framework import viewsets

from lib.boku.constants import TRANS_STATUS_FROM_VERIFY_CODE
from lib.boku.errors import BokuException
from lib.boku.utils import verify
from lib.boku.forms import EventForm
from lib.transactions.constants import STATUS_COMPLETED, STATUSES_INVERTED

from solitude.base import BaseAPIView, log_cef
from solitude.logger import getLogger

log = getLogger("s.boku")


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])