eradicate tastypie (bug 910624)

This commit is contained in:
Allen Short 2013-12-09 13:27:12 -08:00
Родитель 055ccfea5d
Коммит da66cbecaf
26 изменённых файлов: 126 добавлений и 1230 удалений

Просмотреть файл

@ -28,7 +28,6 @@ from nose.exc import SkipTest
from nose.tools import eq_, nottest, ok_
from pyquery import PyQuery as pq
from redisutils import mock_redis, reset_redis
from tastypie.exceptions import ImmediateHttpResponse
from test_utils import RequestFactory
from waffle import cache_sample, cache_switch
from waffle.models import Flag, Sample, Switch

Просмотреть файл

@ -678,7 +678,6 @@ def fudge_headers(response, stats):
patch_cache_control(response, max_age=seven_days)
# TODO: Move to utils and implement tastypie serializer.
class UnicodeCSVDictWriter(csv.DictWriter):
"""A DictWriter that writes a unicode stream."""

Просмотреть файл

@ -122,8 +122,3 @@ def log_configure():
logger['propagate'] = False
dictconfig.dictConfig(cfg)
# logging.getLogger() accesses a singleton, this just binds
# in the SentryHandler to error level messages
tastypie = logging.getLogger('django.request.tastypie')
tastypie.addHandler(SentryHandler())

Просмотреть файл

@ -8,13 +8,12 @@ from nose.tools import eq_
from abuse.models import AbuseReport
from mkt.api.tests.test_oauth import RestOAuth
from mkt.api.tests.test_throttle import ThrottleTests
from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp
from users.models import UserProfile
class BaseTestAbuseResource(ThrottleTests):
class BaseTestAbuseResource(object):
"""
Setup for AbuseResource tests that require inheritance from TestCase.
"""

Просмотреть файл

@ -4,11 +4,10 @@ import json
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django import http
import commonware.log
from rest_framework.authentication import BaseAuthentication
from tastypie import http
from tastypie.authentication import Authentication
import waffle
from access.middleware import ACLMiddleware
@ -26,13 +25,7 @@ class OAuthError(RuntimeError):
self.message = message
errors = {
'headers': 'Error with OAuth headers',
'roles': 'Cannot be a user with roles.',
}
class OAuthAuthentication(Authentication):
class RestOAuthAuthentication(BaseAuthentication):
"""
This is based on https://github.com/amrox/django-tastypie-two-legged-oauth
with permission.
@ -41,10 +34,6 @@ class OAuthAuthentication(Authentication):
def __init__(self, realm='API'):
self.realm = realm
def _error(self, reason):
return http.HttpUnauthorized(content=json.dumps({'reason':
errors[reason]}))
def is_authenticated(self, request, **kwargs):
if not settings.SITE_URL:
raise ValueError('SITE_URL is not specified')
@ -54,7 +43,7 @@ class OAuthAuthentication(Authentication):
'oauth_token' not in request.META['QUERY_STRING']):
self.user = AnonymousUser()
log.error('No header')
return self._error('headers')
return None
auth_header = {'Authorization': auth_header_value}
method = getattr(request, 'signed_method', request.method)
@ -74,7 +63,7 @@ class OAuthAuthentication(Authentication):
if not valid:
log.error(u'Cannot find APIAccess token with that key: %s'
% oauth.attempted_key)
return self._error('headers')
return None
uid = Token.objects.filter(
token_type=ACCESS_TOKEN,
key=oauth_request.resource_owner_key).values_list(
@ -96,7 +85,7 @@ class OAuthAuthentication(Authentication):
if not valid:
log.error(u'Cannot find APIAccess token with that key: %s'
% oauth.attempted_key)
return self._error('headers')
return None
uid = Access.objects.filter(
key=oauth_request.client_key).values_list(
'user_id', flat=True)[0]
@ -124,32 +113,28 @@ class OAuthAuthentication(Authentication):
if roles and roles.intersection(denied_groups):
log.info(u'Attempt to use API with denied role, user: %s'
% request.amo_user.pk)
return self._error('roles')
return None
log.info('Successful OAuth with user: %s' % request.user)
return True
class OptionalOAuthAuthentication(OAuthAuthentication):
"""
Like OAuthAuthentication, but doesn't require there to be
authentication headers. If no headers are provided, just continue
as an anonymous user.
"""
def is_authenticated(self, request, **kw):
auth_header_value = request.META.get('HTTP_AUTHORIZATION', None)
if (not auth_header_value and
'oauth_token' not in request.META['QUERY_STRING']):
request.user = AnonymousUser()
log.info('Successful OptionalOAuth as anonymous user')
return True
return (super(OptionalOAuthAuthentication, self)
.is_authenticated(request, **kw))
def authenticate(self, request):
# The DRF Request object wraps the actual WSGIRequest. Its
# 'method' attribute is a property. when using
# X-HTTP-Method-Override, request.method will return the
# overriding method instead of POST. OAuth signatures include
# the actual HTTP method, so we pass the unwrapped WSGIRequest
# for authentication.
result = self.is_authenticated(request._request)
user = getattr(request._request, 'user', None)
if user:
request._user = user
if (not result or not request.user):
return None
return (request.user, None)
class SharedSecretAuthentication(Authentication):
class RestSharedSecretAuthentication(BaseAuthentication):
def is_authenticated(self, request, **kwargs):
header = request.META.get('HTTP_AUTHORIZATION', '').split(None, 1)
@ -195,31 +180,6 @@ class SharedSecretAuthentication(Authentication):
log.info('Bad shared-secret auth data: %s (%s)', auth, e)
return False
class RestOAuthAuthentication(BaseAuthentication, OAuthAuthentication):
"""OAuthAuthentication suitable for DRF, wraps around tastypie ones."""
def authenticate(self, request):
# The DRF Request object wraps the actual WSGIRequest. Its
# 'method' attribute is a property. when using
# X-HTTP-Method-Override, request.method will return the
# overriding method instead of POST. OAuth signatures include
# the actual HTTP method, so we pass the unwrapped WSGIRequest
# for authentication.
result = self.is_authenticated(request._request)
user = getattr(request._request, 'user', None)
if user:
request._user = user
if (not result or isinstance(result, http.HttpUnauthorized)
or not request.user):
return None
return (request.user, None)
class RestSharedSecretAuthentication(BaseAuthentication,
SharedSecretAuthentication):
"""SharedSecretAuthentication suitable for DRF."""
def authenticate(self, request):
result = self.is_authenticated(request._request)
user = getattr(request._request, 'user', None)

Просмотреть файл

@ -3,7 +3,6 @@ from collections import defaultdict
import commonware.log
from rest_framework.permissions import BasePermission, SAFE_METHODS
from tastypie.authorization import Authorization, ReadOnlyAuthorization
from waffle import flag_is_active, switch_is_active
from access import acl
@ -11,92 +10,6 @@ from access import acl
log = commonware.log.getLogger('z.api')
class OwnerAuthorization(Authorization):
def is_authorized(self, request, object=None):
# There is no object being passed, so we'll assume it's ok
if not object:
return True
# There is no request user or no user on the object.
if not request.amo_user:
return False
return self.check_owner(request, object)
def check_owner(self, request, object):
if not object.user:
return False
# If the user on the object and the amo_user match, we are golden.
return object.user.pk == request.amo_user.pk
class AppOwnerAuthorization(OwnerAuthorization):
def check_owner(self, request, object):
# If the user on the object and the amo_user match, we are golden.
try:
if object.authors.filter(user__id=request.amo_user.pk):
return True
# Appropriately handles AnonymousUsers when `amo_user` is None.
except AttributeError:
return False
# Reviewers can see non-public apps.
if request.method == 'GET':
if acl.action_allowed(request, 'Apps', 'Review'):
return True
class AnonymousReadOnlyAuthorization(ReadOnlyAuthorization):
"""
Allows read-only access for anonymous users and optional auth for
authenticated users.
If the user is anonymous, only ``GET`` requests are allowed.
If the user is authenticated, a custom authorization object can be used.
If no object is set, authenticated users always have access.
Keyword Arguments
**authorizer=None**
If set, this authorization object will be used to check the action for
authenticated users.
"""
def __init__(self, *args, **kw):
self.authorizer = kw.pop('authorizer', None)
super(AnonymousReadOnlyAuthorization, self).__init__(*args, **kw)
def is_authorized(self, request, object=None):
if request.user.is_anonymous():
sup = super(AnonymousReadOnlyAuthorization, self)
res = sup.is_authorized(request, object)
log.info('ReadOnlyAuthorization returned: %s' % res)
return res
if self.authorizer:
res = self.authorizer.is_authorized(request, object)
log.info('Authorizer %s returned: %s' %
(self.authorizer.__class__.__name__, res))
return res
return True
class PermissionAuthorization(Authorization):
def __init__(self, app, action, *args, **kw):
self.app, self.action = app, action
def is_authorized(self, request, object=None):
if acl.action_allowed(request, self.app, self.action):
return True
log.info('Permission authorization failed')
return False
has_permission = is_authorized
class AnyOf(BasePermission):
"""
Takes multiple permission objects and succeeds if any single one does.

Просмотреть файл

@ -1,16 +1,8 @@
import functools
import json
import logging
import sys
import traceback
from collections import defaultdict
from django.conf import settings
from django.core.urlresolvers import reverse
from django.conf.urls.defaults import url
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models.sql import EmptyResultSet
from django.http import HttpResponseNotFound
import commonware.log
from rest_framework.decorators import api_view
@ -19,23 +11,8 @@ from rest_framework.mixins import ListModelMixin
from rest_framework.routers import Route, SimpleRouter
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from tastypie import fields, http
from tastypie.bundle import Bundle
from tastypie.exceptions import (ImmediateHttpResponse, NotFound,
UnsupportedFormat)
from tastypie.fields import ToOneField
from tastypie.http import HttpConflict
from tastypie.resources import ModelResource, Resource
from access import acl
from translations.fields import PurifiedField, TranslatedField
from .exceptions import AlreadyPurchased, DeserializationError
from .http import HttpTooManyRequests
from .serializers import Serializer
log = commonware.log.getLogger('z.api')
tasty_log = logging.getLogger('django.request.tastypie')
def list_url(name, **kw):
@ -47,40 +24,6 @@ def get_url(name, pk, **kw):
kw.update({'resource_name': name, 'pk': pk})
return ('api_dispatch_detail', kw)
def http_error(errorclass, reason, extra_data=None):
response = errorclass()
data = {'reason': reason}
if extra_data:
data.update(extra_data)
response.content = json.dumps(data)
return ImmediateHttpResponse(response)
def handle_500(resource, request, exception):
response_class = http.HttpApplicationError
if isinstance(exception, (NotFound, ObjectDoesNotExist)):
response_class = HttpResponseNotFound
# Print some nice 500 errors back to the clients if not in debug mode.
exc_info = sys.exc_info()
tasty_log.error('%s: %s %s\n%s' % (request.path,
exception.__class__.__name__,
exception,
traceback.format_tb(exc_info[2])),
extra={'status_code': 500, 'request': request},
exc_info=exc_info)
data = {
'error_message': str(exception),
'error_code': getattr(exception, 'id',
exception.__class__.__name__),
'error_data': getattr(exception, 'data', {})
}
serialized = resource.serialize(request, data, 'application/json')
return response_class(content=serialized,
content_type='application/json; charset=utf-8')
def _collect_form_errors(forms):
errors = {}
if not isinstance(forms, list):
@ -101,327 +44,6 @@ def form_errors(forms):
errors = _collect_form_errors(forms)
raise ParseError(errors)
class Marketplace(object):
"""
A mixin with some general Marketplace stuff.
"""
class Meta(object):
serializer = Serializer()
def _handle_500(self, request, exception):
return handle_500(self, request, exception)
def dispatch(self, request_type, request, **kwargs):
# OAuth authentication uses the method in the signature. So we need
# to store the original method used to sign the request.
request.signed_method = request.method
if 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META:
request.method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE']
log.info('Request: %s' % request.META.get('PATH_INFO'))
ct = request.META.get('CONTENT_TYPE')
try:
return (super(Marketplace, self)
.dispatch(request_type, request, **kwargs))
except DeserializationError:
if ct:
error = "Unable to deserialize request body as '%s'" % ct
else:
error = 'Content-Type header required'
raise self.non_form_errors((('__all__', error),),)
except UnsupportedFormat:
msgs = []
if ct not in self._meta.serializer.supported_formats:
msgs.append(('__all__',
"Unsupported Content-Type header '%s'" % ct))
accept = request.META.get('HTTP_ACCEPT')
if accept and accept != 'application/json':
msgs.append(('__all__',
"Unsupported Accept header '%s'" % accept))
raise self.non_form_errors(msgs)
except PermissionDenied:
# Reraise PermissionDenied as 403, otherwise you get 500.
raise http_error(http.HttpForbidden, 'Permission denied.')
except AlreadyPurchased:
raise http_error(HttpConflict, 'Already purchased app.')
def non_form_errors(self, error_list):
"""
Raises passed field errors as an immediate HttpBadRequest response.
Similar to Marketplace.form_errors, except that it allows you to raise
form field errors outside of form validation.
Accepts a list of two-tuples, consisting of a field name and error
message.
Example usage:
errors = []
if 'app' in bundle.data:
errors.append(('app', 'Cannot update the app of a rating.'))
if 'user' in bundle.data:
errors.append(('user', 'Cannot update the author of a rating.'))
if errors:
raise self.non_form_errors(errors)
"""
errors = defaultdict(list)
for e in error_list:
errors[e[0]].append(e[1])
response = http.HttpBadRequest(json.dumps({'error_message': errors}),
content_type='application/json')
return ImmediateHttpResponse(response=response)
def form_errors(self, forms):
errors = _collect_form_errors(forms)
response = http.HttpBadRequest(json.dumps({'error_message': errors}),
content_type='application/json')
return ImmediateHttpResponse(response=response)
def _auths(self):
auths = self._meta.authentication
if not isinstance(auths, (list, tuple)):
auths = [self._meta.authentication]
return auths
def is_authenticated(self, request):
"""
An override of the tastypie Authentication to accept an iterator
of Authentication methods. If so it will go through in order, when one
passes, it will use that.
Any authentication method can still return a HttpResponse to break out
of the loop if they desire.
"""
for auth in self._auths():
log.info('Trying authentication with %s' % auth.__class__.__name__)
auth_result = auth.is_authenticated(request)
if isinstance(auth_result, http.HttpResponse):
raise ImmediateHttpResponse(response=auth_result)
if auth_result:
log.info('Logged in using %s' % auth.__class__.__name__)
return
raise http_error(http.HttpUnauthorized, 'Authentication required.')
def get_throttle_identifiers(self, request):
return set(a.get_identifier(request) for a in self._auths())
def throttle_check(self, request):
"""
Handles checking if the user should be throttled.
Mostly a hook, this uses class assigned to ``throttle`` from
``Resource._meta``.
"""
# Never throttle users with Apps:APIUnthrottled or "safe" requests.
if (not settings.API_THROTTLE or
request.method in ('GET', 'HEAD', 'OPTIONS') or
acl.action_allowed(request, 'Apps', 'APIUnthrottled')):
return
identifiers = self.get_throttle_identifiers(request)
# Check to see if they should be throttled.
if any(self._meta.throttle.should_be_throttled(identifier)
for identifier in identifiers):
# Throttle limit exceeded.
raise http_error(HttpTooManyRequests,
'Throttle limit exceeded.')
def log_throttled_access(self, request):
"""
Handles the recording of the user's access for throttling purposes.
Mostly a hook, this uses class assigned to ``throttle`` from
``Resource._meta``.
"""
request_method = request.method.lower()
identifiers = self.get_throttle_identifiers(request)
for identifier in identifiers:
self._meta.throttle.accessed(identifier,
url=request.get_full_path(),
request_method=request_method)
def cached_obj_get_list(self, request=None, **kwargs):
"""Do not interfere with cache machine caching."""
return self.obj_get_list(request=request, **kwargs)
def cached_obj_get(self, request=None, **kwargs):
"""Do not interfere with cache machine caching."""
return self.obj_get(request, **kwargs)
def is_valid(self, bundle, request=None):
"""A simple wrapper to return form errors in the format we want."""
errors = self._meta.validation.is_valid(bundle, request)
if errors:
raise self.form_errors(errors)
def dehydrate_objects(self, objects, request=None):
"""
Dehydrates each object using the full_dehydrate and then
returns the data for each object. This is useful for compound
results that return sub objects data. If you need request in the
dehydration, pass that through (eg: accessing region)
"""
return [self.full_dehydrate(Bundle(obj=o, request=request)).data
for o in objects]
class MarketplaceResource(Marketplace, Resource):
"""
Use this if you would like to expose something that is *not* a Django
model as an API.
"""
def get_resource_uri(self, *args, **kw):
return ''
class MarketplaceModelResource(Marketplace, ModelResource):
"""Use this if you would like to expose a Django model as an API."""
def get_resource_uri(self, bundle_or_obj):
# Fix until my pull request gets pulled into tastypie.
# https://github.com/toastdriven/django-tastypie/pull/490
kwargs = {
'resource_name': self._meta.resource_name,
}
if isinstance(bundle_or_obj, Bundle):
kwargs['pk'] = bundle_or_obj.obj.pk
else:
kwargs['pk'] = bundle_or_obj.pk
if self._meta.api_name is not None:
kwargs['api_name'] = self._meta.api_name
return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)
@classmethod
def should_skip_field(cls, field):
# We don't want to skip translated fields.
if isinstance(field, (PurifiedField, TranslatedField)):
return False
return True if getattr(field, 'rel') else False
def get_object_or_404(self, cls, **filters):
"""
A wrapper around our more familiar get_object_or_404, for when we need
to get access to an object that isn't covered by get_obj.
"""
if not filters:
raise http_error(http.HttpNotFound, 'Not found.')
try:
return cls.objects.get(**filters)
except (cls.DoesNotExist, cls.MultipleObjectsReturned):
raise http_error(http.HttpNotFound, 'Not found.')
def get_by_resource_or_404(self, request, **kwargs):
"""
A wrapper around the obj_get to just get the object.
"""
try:
obj = self.obj_get(request, **kwargs)
except ObjectDoesNotExist:
raise http_error(http.HttpNotFound, 'Not found.')
return obj
def base_urls(self):
"""
If `slug_lookup` is specified on the Meta of a resource, add
in an extra resource that allows lookup by that slug field. This
assumes that the slug won't be all numbers. If the slug is numeric, it
will hit the pk URL pattern and chaos will ensue.
"""
if not getattr(self._meta, 'slug_lookup', None):
return super(MarketplaceModelResource, self).base_urls()
return super(MarketplaceModelResource, self).base_urls()[:3] + [
url(r'^(?P<resource_name>%s)/(?P<pk>\d+)/$' %
self._meta.resource_name,
self.wrap_view('dispatch_detail'),
name='api_dispatch_detail'),
url(r"^(?P<resource_name>%s)/(?P<%s>[^/<>\"']+)/$" %
(self._meta.resource_name, self._meta.slug_lookup),
self.wrap_view('dispatch_detail'),
name='api_dispatch_detail')
]
class GenericObject(dict):
"""
tastypie-friendly subclass of dict that allows direct attribute assignment
of dict items. Best used as `object_class` when not using a `ModelResource`
subclass.
"""
def __getattr__(self, name):
try:
return self.__getitem__(name)
except KeyError:
return None
def __setattr__(self, name, value):
self.__setitem__(name, value)
class CORSResource(object):
"""
A mixin to provide CORS support to your API.
"""
def method_check(self, request, allowed=None):
"""
This is the first entry point from dispatch and a place to check CORS.
It will set a value on the request for the middleware to pick up on
the response and add in the headers, so that any immediate http
responses (which are usually errors) get the headers.
Optionally, you can specify the methods that will be specifying the
`cors_allowed` attribute on the resource meta. Otherwise, it will use
the combination of allowed_methods specified on the resource.
"""
request.CORS = getattr(self._meta, 'cors_allowed', None) or allowed
return super(CORSResource, self).method_check(request, allowed=allowed)
class PotatoCaptchaResource(object):
"""
A mixin adding the fields required by PotatoCaptcha to the resource.
"""
tuber = fields.CharField(attribute='tuber')
sprout = fields.CharField(attribute='sprout')
def remove_potato(self, bundle):
for field in ['tuber', 'sprout']:
if field in bundle.data:
del bundle.data[field]
return bundle
def alter_detail_data_to_serialize(self, request, data):
"""
Remove `sprout` from bundle data before returning serialized object to
the consumer.
"""
sup = super(PotatoCaptchaResource, self)
bundle = sup.alter_detail_data_to_serialize(request, data)
return self.remove_potato(bundle)
def check_potatocaptcha(data):
if data.get('tuber', False):
return Response(json.dumps({'tuber': 'Invalid value'}), 400)
@ -429,31 +51,6 @@ def check_potatocaptcha(data):
return Response(json.dumps({'sprout': 'Invalid value'}), 400)
class CompatToOneField(ToOneField):
"""
Tastypie field to relate a resource to a django-rest-framework view.
"""
def __init__(self, *args, **kwargs):
self.url_name = kwargs.pop('url_name', None)
self.extra_fields = kwargs.pop('extra_fields', None)
return super(CompatToOneField, self).__init__(*args, **kwargs)
def dehydrate_related(self, bundle, related_resource):
uri = reverse(self.url_name, kwargs={'pk': bundle.obj.pk})
if self.full:
raise NotImplementedError
elif self.extra_fields:
result = {'resource_uri': uri}
for field in self.extra_fields:
result[field] = getattr(bundle.obj, field)
return result
else:
return uri
def get_related_resource(self, related_instance):
return
class AppRouter(SimpleRouter):
routes = [
# List route.
@ -610,21 +207,3 @@ class SilentListModelMixin(ListModelMixin):
if res.status_code == 404:
return Response([])
return res
class AppViewSet(GenericViewSet):
def initialize_request(self, request, *args, **kwargs):
"""
Pass the value in the URL through to the form defined on the
ViewSet, which will populate the app property with the app object.
You must define a form which will take an app object.
"""
request = (super(AppViewSet, self)
.initialize_request(request, *args, **kwargs))
self.app = None
form = self.form({'app': kwargs.get('pk')})
if form.is_valid():
self.app = form.cleaned_data['app']
return request

Просмотреть файл

@ -5,12 +5,6 @@ from rest_framework import status
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.views import exception_handler
from tastypie.exceptions import TastypieError
class DeserializationError(TastypieError):
def __init__(self, original=None):
self.original = original
class AlreadyPurchased(Exception):

Просмотреть файл

@ -5,7 +5,6 @@ import StringIO
from django import forms
import happyforms
from tastypie.validation import CleanedDataFormValidation
from tower import ugettext_lazy as _lazy
import amo
@ -49,25 +48,6 @@ class SluggableModelChoiceField(forms.ModelChoiceField):
return super(SluggableModelChoiceField, self).to_python(value)
class RequestFormValidation(CleanedDataFormValidation):
"""
A sub class of CleanedDataFormValidation that passes request through to
the form.
"""
def is_valid(self, bundle, request=None):
data = bundle.data
if data is None:
data = {}
form = self.form_class(data, request=request)
if form.is_valid():
bundle.data = form.cleaned_data
return {}
return form.errors
def parse(file_, require_name=False, require_type=None):
try:
if not set(['data', 'type']).issubset(set(file_.keys())):

Просмотреть файл

@ -4,37 +4,12 @@ from django.utils.simplejson import JSONDecodeError
import commonware.log
from rest_framework import serializers
from rest_framework.reverse import reverse
from tastypie.serializers import Serializer
from tastypie.exceptions import UnsupportedFormat
from tower import ugettext as _
from mkt.api.exceptions import DeserializationError
log = commonware.log.getLogger('z.mkt.api.forms')
class Serializer(Serializer):
formats = ['json', 'urlencode']
content_types = {
'json': 'application/json',
'urlencode': 'application/x-www-form-urlencoded',
}
def from_urlencode(self, data):
return QueryDict(data).dict()
def to_urlencode(self, data, options=None):
raise UnsupportedFormat
def deserialize(self, content, format='application/json'):
try:
return super(Serializer, self).deserialize(content, format)
except JSONDecodeError, exc:
raise DeserializationError(original=exc)
class PotatoCaptchaSerializer(serializers.Serializer):
"""
Serializer class to inherit from to get PotatoCaptcha (tm) protection for

Просмотреть файл

@ -1,5 +1,4 @@
from datetime import datetime
import json
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
@ -8,88 +7,25 @@ from mock import Mock, patch
from multidb import this_thread_is_pinned
from nose.tools import eq_, ok_
from rest_framework.request import Request
from tastypie.exceptions import ImmediateHttpResponse
from access.models import Group, GroupUser
from addons.models import AddonUser
from amo.helpers import absolutify
from amo.tests import app_factory, TestCase
from amo.tests import TestCase
from amo.urlresolvers import reverse
from test_utils import RequestFactory
from users.models import UserProfile
from mkt.api import authentication, authorization
from mkt.api.authentication import errors
from mkt.api.base import MarketplaceResource
from mkt.api import authentication
from mkt.api.models import Access, generate
from mkt.api.tests.test_oauth import OAuthClient
from mkt.site.fixtures import fixture
class OwnerAuthorization(TestCase):
fixtures = fixture('user_2519')
def setUp(self):
self.profile = UserProfile.objects.get(pk=2519)
self.user = self.profile.user
def request(self, user=None):
return Mock(amo_user=user, groups=user.groups.all() if user else None)
def obj(self, user=None):
return Mock(user=user)
class TestOwnerAuthorization(OwnerAuthorization):
def setUp(self):
super(TestOwnerAuthorization, self).setUp()
self.auth = authorization.OwnerAuthorization()
def test_user(self):
ok_(self.auth.check_owner(self.request(self.profile),
self.obj(self.user)))
def test_not_user(self):
ok_(not self.auth.check_owner(self.request(self.profile),
self.obj()))
def test_diff_user(self):
user = Mock()
user.pk = self.user.pk + 1
ok_(not self.auth.check_owner(self.request(self.profile),
self.obj(user)))
def test_no_object(self):
ok_(self.auth.is_authorized(None))
def test_no_user(self):
ok_(not self.auth.is_authorized(self.request(None), True))
class TestAppOwnerAuthorization(OwnerAuthorization):
def setUp(self):
super(TestAppOwnerAuthorization, self).setUp()
self.auth = authorization.AppOwnerAuthorization()
self.app = app_factory()
def test_owner(self):
AddonUser.objects.create(addon=self.app, user=self.profile)
ok_(self.auth.check_owner(self.request(self.profile),
self.app))
def test_not_owner(self):
ok_(not self.auth.check_owner(self.request(self.profile),
self.app))
class TestOAuthAuthentication(TestCase):
fixtures = fixture('user_2519', 'group_admin', 'group_editor')
def setUp(self):
self.api_name = 'foo'
self.auth = authentication.OAuthAuthentication()
self.profile = UserProfile.objects.get(pk=2519)
self.profile.update(read_dev_agreement=datetime.today())
self.access = Access.objects.create(key='test_oauth_key',
@ -103,37 +39,11 @@ class TestOAuthAuthentication(TestCase):
HTTP_HOST='testserver',
HTTP_AUTHORIZATION=client.sign('GET', url)[1]['Authorization'])
def test_accepted(self):
req = Request(self.call())
ok_(self.auth.is_authenticated(req))
if req.method in ['DELETE', 'PATCH', 'POST', 'PUT']:
ok_(this_thread_is_pinned())
else:
ok_(not this_thread_is_pinned())
def test_request_token_fake(self):
c = Mock()
c.key = self.access.key
c.secret = 'mom'
res = self.auth.is_authenticated(self.call(client=OAuthClient(c)))
eq_(res.status_code, 401)
eq_(json.loads(res.content)['reason'], errors['headers'])
def add_group_user(self, user, *names):
for name in names:
group = Group.objects.get(name=name)
GroupUser.objects.create(user=self.profile, group=group)
def test_request_admin(self):
self.add_group_user(self.profile, 'Admins')
res = self.auth.is_authenticated(self.call())
eq_(res.status_code, 401)
eq_(json.loads(res.content)['reason'], errors['roles'])
def test_request_has_role(self):
self.add_group_user(self.profile, 'App Reviewers')
ok_(self.auth.is_authenticated(self.call()))
class TestRestOAuthAuthentication(TestOAuthAuthentication):
@ -182,7 +92,7 @@ class TestSharedSecretAuthentication(TestCase):
fixtures = fixture('user_2519')
def setUp(self):
self.auth = authentication.SharedSecretAuthentication()
self.auth = authentication.RestSharedSecretAuthentication()
self.profile = UserProfile.objects.get(pk=2519)
self.profile.update(email=self.profile.user.email)
@ -235,79 +145,6 @@ class TestSharedSecretAuthentication(TestCase):
assert not self.auth.is_authenticated(req)
class TestOptionalOAuthAuthentication(TestCase):
def setUp(self):
self.auth = authentication.OptionalOAuthAuthentication()
def test_none(self):
req = RequestFactory().get('/')
ok_(self.auth.is_authenticated(req))
def test_something(self):
# Malformed auth info is rejected.
req = RequestFactory().get('/', HTTP_AUTHORIZATION='No!')
ok_(not self.auth.is_authenticated(req))
class MultipleTestResource(MarketplaceResource):
pass
@patch.object(settings, 'SECRET_KEY', 'gubbish')
class TestMultipleAuthentication(TestCase):
fixtures = fixture('user_2519')
def setUp(self):
self.resource = MultipleTestResource()
self.profile = UserProfile.objects.get(pk=2519)
self.profile.update(email=self.profile.user.email)
def test_single(self):
req = RequestFactory().get(
'/',
HTTP_AUTHORIZATION='mkt-shared-secret '
'cfinke@m.com,56b6f1a3dd735d962c56'
'ce7d8f46e02ec1d4748d2c00c407d75f0969d08bb'
'9c68c31b3371aa8130317815c89e5072e31bb94b4'
'121c5c165f3515838d4d6c60c4,165d631d3c3045'
'458b4516242dad7ae')
self.resource._meta.authentication = (
authentication.SharedSecretAuthentication(),)
eq_(self.resource.is_authenticated(req), None)
eq_(self.profile.user.pk, req.amo_user.pk)
def test_multiple_passes(self):
req = RequestFactory().get('/')
req.user = AnonymousUser()
self.resource._meta.authentication = (
authentication.SharedSecretAuthentication(),
# Optional auth passes because there are not auth headers.
authentication.OptionalOAuthAuthentication())
eq_(self.resource.is_authenticated(req), None)
def test_multiple_fails(self):
client = OAuthClient(Mock(key='test_oauth_key',
secret='test_oauth_secret'))
req = RequestFactory().get(
'/',
HTTP_HOST='testserver',
HTTP_AUTHORIZATION=client.sign(
'GET', 'http://foo/')[1]['Authorization'])
req.user = AnonymousUser()
next_auth = Mock()
self.resource._meta.authentication = (
# OAuth fails because there are bogus auth headers.
authentication.OAuthAuthentication(),
next_auth)
with self.assertRaises(ImmediateHttpResponse):
eq_(self.resource.is_authenticated(req), None)
# This never even got called.
ok_(not next_auth.is_authenticated.called)
@patch.object(settings, 'SECRET_KEY', 'gubbish')
class TestMultipleAuthenticationDRF(TestCase):
fixtures = fixture('user_2519')

Просмотреть файл

@ -5,73 +5,16 @@ from mock import Mock
from nose.tools import eq_, ok_
from test_utils import RequestFactory
from amo.tests import app_factory, TestCase
from amo.tests import TestCase
from users.models import UserProfile
from mkt.api.authorization import (AllowAppOwner, AllowNone, AllowOwner,
AllowRelatedAppOwner, AllowSelf,
AnonymousReadOnlyAuthorization, AnyOf,
ByHttpMethod, flag, GroupPermission,
PermissionAuthorization, switch)
AnyOf,
ByHttpMethod, flag, GroupPermission, switch)
from mkt.site.fixtures import fixture
from mkt.webapps.models import Webapp
from .test_authentication import OwnerAuthorization
class TestAnonymousReadOnlyAuthorization(TestCase):
fixtures = fixture('user_2519')
def setUp(self):
self.get = RequestFactory().get('/')
self.post = RequestFactory().post('/')
self.auth = AnonymousReadOnlyAuthorization()
self.anon = AnonymousUser()
self.user = User.objects.get(pk=2519)
def test_get_anonymous(self):
self.get.user = self.anon
eq_(self.auth.is_authorized(self.get), True)
def test_get_authenticated(self):
self.get.user = self.user
eq_(self.auth.is_authorized(self.get), True)
def test_post_anonymous(self):
self.post.user = self.anon
eq_(self.auth.is_authorized(self.post), False)
def test_post_authenticated(self):
self.post.user = self.user
eq_(self.auth.is_authorized(self.post), True)
def test_with_authorizer(self):
class LockedOut:
def is_authorized(self, request, object=None):
return False
self.auth = AnonymousReadOnlyAuthorization(
authorizer=LockedOut())
self.post.user = self.user
eq_(self.auth.is_authorized(self.post), False)
class TestPermissionAuthorization(OwnerAuthorization):
def setUp(self):
super(TestPermissionAuthorization, self).setUp()
self.auth = PermissionAuthorization('Drinkers', 'Beer')
self.app = app_factory()
def test_has_role(self):
self.grant_permission(self.profile, 'Drinkers:Beer')
ok_(self.auth.is_authorized(self.request(self.profile), self.app))
def test_not_has_role(self):
self.grant_permission(self.profile, 'Drinkers:Scotch')
ok_(not self.auth.is_authorized(self.request(self.profile), self.app))
class TestWaffle(TestCase):

Просмотреть файл

@ -1,43 +1,22 @@
import json
import urllib
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from mock import patch
from nose.tools import eq_, ok_
from nose.tools import eq_
from rest_framework.decorators import (authentication_classes,
permission_classes)
from rest_framework.response import Response
from tastypie import http
from tastypie.authentication import Authentication
from tastypie.authorization import Authorization
from tastypie.exceptions import ImmediateHttpResponse
from tastypie.throttle import BaseThrottle
from test_utils import RequestFactory
from access.middleware import ACLMiddleware
from amo.tests import TestCase
from mkt.api.base import (AppViewSet, cors_api_view, CORSResource, handle_500,
MarketplaceResource)
from mkt.api.http import HttpTooManyRequests
from mkt.api.serializers import Serializer
from mkt.receipts.tests.test_views import RawRequestFactory
from mkt.site.fixtures import fixture
from amo.urlresolvers import reverse
class SampleResource(MarketplaceResource):
class Meta(object):
authorization = Authorization()
serializer = Serializer()
object_class = dict
def get_resource_uri(self, bundle):
return ''
from mkt.api.base import cors_api_view
from mkt.api.tests.test_oauth import RestOAuth
from mkt.webapps.api import AppViewSet
class URLRequestFactory(RequestFactory):
@ -46,191 +25,37 @@ class URLRequestFactory(RequestFactory):
return urllib.urlencode(data)
class TestLogging(TestCase):
def setUp(self):
self.resource = SampleResource()
self.request = URLRequestFactory().get('/')
self.exception_cls = type('SampleException', (Exception,), {})
@patch('mkt.api.base.tasty_log.error')
def test_logging(self, mock_error_log):
msg = 'oops'
handle_500(self.resource, self.request, self.exception_cls(msg))
eq_(mock_error_log.call_count, 1)
ok_(self.exception_cls.__name__ in mock_error_log.call_args[0][0])
ok_(msg in mock_error_log.call_args[0][0])
ok_('exc_info' in mock_error_log.call_args[1])
class TestEncoding(TestCase):
def setUp(self):
self.resource = SampleResource()
self.request = URLRequestFactory().post('/')
class TestEncoding(RestOAuth):
def test_blah_encoded(self):
"""
Regression test of bug #858403: ensure that a 400 (and not 500) is
Regression test of bug #858403: ensure that a 415 (and not 500) is
raised when an unsupported Content-Type header is passed to an API
endpoint.
"""
self.request.META['CONTENT_TYPE'] = 'application/blah'
with self.assertImmediate(http.HttpBadRequest):
self.resource.dispatch('list', self.request)
def test_no_contenttype(self):
del self.request.META['CONTENT_TYPE']
with self.assertImmediate(http.HttpBadRequest):
self.resource.dispatch('list', self.request)
r = self.client.post(reverse('app-list'),
CONTENT_TYPE='application/blah',
data='cvan was here')
eq_(r.status_code, 415)
def test_bad_json(self):
request = RawRequestFactory().post('/', "not ' json ' 5")
with self.assertImmediate(http.HttpBadRequest):
self.resource.dispatch('list', request)
r = self.client.post(reverse('app-list'),
CONTENT_TYPE='application/json',
data="not ' json ' 5")
eq_(r.status_code, 400)
def test_not_json(self):
self.request.META['HTTP_ACCEPT'] = 'application/blah'
with self.assertImmediate(http.HttpBadRequest):
self.resource.dispatch('list', self.request)
r = self.client.get(reverse('app-list'),
HTTP_ACCEPT='application/blah')
eq_(r.status_code, 406)
def test_errors(self):
self.request.META['HTTP_ACCEPT'] = 'application/blah'
self.request.META['CONTENT_TYPE'] = 'application/blah'
try:
self.resource.dispatch('list', self.request)
except ImmediateHttpResponse, error:
pass
res = json.loads(error.response.content)['error_message']['__all__']
eq_([u"Unsupported Content-Type header 'application/blah'",
u"Unsupported Accept header 'application/blah'"], res)
@patch.object(SampleResource, 'obj_create')
def test_form_encoded(self, obj_create):
request = URLRequestFactory().post('/', data={'foo': 'bar'},
content_type='application/x-www-form-urlencoded')
self.resource.dispatch('list', request)
eq_(obj_create.call_args[0][0].data, {'foo': 'bar'})
@patch.object(SampleResource, 'obj_create')
def test_permission(self, obj_create):
request = RequestFactory().post('/', data={},
content_type='application/json')
obj_create.side_effect = PermissionDenied
with self.assertImmediate(http.HttpForbidden):
self.resource.dispatch('list', request)
class FakeAuthentication(Authentication):
def get_identifier(self, request):
return 'fake'
class ThrottleResource(MarketplaceResource):
class Meta(object):
authorization = Authorization()
throttle = BaseThrottle()
class TestThrottling(TestCase):
fixtures = fixture('user_2519')
def setUp(self):
super(TestThrottling, self).setUp()
self.resource = ThrottleResource()
self.request = RequestFactory().post('/')
self.user = User.objects.get(pk=2519)
self.request.user = self.user
self.throttle = self.resource._meta.throttle
self.request.META['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
self.mocked_sbt = patch.object(self.throttle, 'should_be_throttled')
def no_throttle_expected(self, request=None):
if request is None:
request = self.request
try:
self.resource.throttle_check(request)
except ImmediateHttpResponse, e:
if isinstance(e.response, HttpTooManyRequests):
self.fail('Unexpected 429')
raise e
def throttle_expected(self):
with self.assertImmediate(HttpTooManyRequests):
self.resource.throttle_check(self.request)
def test_get_throttle_identifiers_multiple_auth(self):
self.resource._meta.authentication = [FakeAuthentication(),
FakeAuthentication()]
identifiers = list(self.resource.get_throttle_identifiers(self.request))
eq_(identifiers, ['fake'])
def test_should_throttle(self):
with self.mocked_sbt as sbt:
sbt.return_value = True
self.throttle_expected()
eq_(self.throttle.should_be_throttled.call_count, 1)
def test_shouldnt_throttle(self):
with self.mocked_sbt as sbt:
sbt.return_value = False
self.no_throttle_expected()
eq_(self.throttle.should_be_throttled.call_count, 1)
def test_GET_shouldnt_throttle(self):
with self.mocked_sbt as sbt:
sbt.return_value = True
self.no_throttle_expected(RequestFactory().get('/'))
eq_(self.throttle.should_be_throttled.call_count, 0)
def test_unthrottled_user(self):
self.grant_permission(self.user.get_profile(), 'Apps:APIUnthrottled')
ACLMiddleware().process_request(self.request)
with self.mocked_sbt as sbt:
sbt.return_value = True
self.no_throttle_expected()
eq_(self.throttle.should_be_throttled.call_count, 0)
def test_throttled_user_setting_enabled(self):
with self.settings(API_THROTTLE=True):
ACLMiddleware().process_request(self.request)
with self.mocked_sbt as sbt:
sbt.return_value = True
self.throttle_expected()
eq_(self.throttle.should_be_throttled.call_count, 1)
def test_throttled_user_setting_disabled(self):
with self.settings(API_THROTTLE=False):
ACLMiddleware().process_request(self.request)
with self.mocked_sbt as sbt:
sbt.return_value = True
self.no_throttle_expected()
eq_(self.throttle.should_be_throttled.call_count, 0)
class FilteredCORS(CORSResource, MarketplaceResource):
class Meta(object):
cors_allowed = ['get', 'put']
class UnfilteredCORS(CORSResource, MarketplaceResource):
pass
class TestCORSResource(TestCase):
def test_filtered(self):
request = RequestFactory().get('/')
FilteredCORS().method_check(request, allowed=['get'])
eq_(request.CORS, ['get', 'put'])
def test_unfiltered(self):
request = RequestFactory().get('/')
UnfilteredCORS().method_check(request, allowed=['get'])
eq_(request.CORS, ['get'])
@patch.object(AppViewSet, 'create')
def test_form_encoded(self, create_mock):
create_mock.return_value = Response()
self.client.post(reverse('app-list'),
data='foo=bar',
content_type='application/x-www-form-urlencoded')
eq_(create_mock.call_args[0][0].DATA['foo'], 'bar')
class TestCORSWrapper(TestCase):
@ -247,20 +72,3 @@ class TestCORSWrapper(TestCase):
class Form(forms.Form):
app = forms.ChoiceField(choices=(('valid', 'valid'),))
class TestAppViewSet(TestCase):
def setUp(self):
self.request = RequestFactory().get('/')
self.viewset = AppViewSet()
self.viewset.action_map = {}
self.viewset.form = Form
def test_ok(self):
self.viewset.initialize_request(self.request, pk='valid')
ok_(self.viewset.app)
def test_not_ok(self):
self.viewset.initialize_request(self.request, pk='invalid')
eq_(self.viewset.app, None)

Просмотреть файл

@ -19,7 +19,6 @@ from amo.helpers import absolutify, urlparams
from amo.urlresolvers import reverse
from mkt.api import authentication
from mkt.api.base import CORSResource, MarketplaceResource
from mkt.api.models import Access, Token, generate, REQUEST_TOKEN, ACCESS_TOKEN
from mkt.api.tests import BaseAPI
from mkt.site.fixtures import fixture
@ -169,23 +168,6 @@ class RestOAuth(BaseOAuth):
self.anon = RestOAuthClient(None)
class Resource(CORSResource, MarketplaceResource):
class Meta:
list_allowed_method = ['get']
class TestCORS(BaseOAuth):
def setUp(self):
self.resource = Resource()
def test_cors(self):
req = RequestFactory().get('/')
self.resource.method_check(req, allowed=['get'])
eq_(req.CORS, ['get'])
class Test3LeggedOAuthFlow(TestCase):
fixtures = fixture('user_2519', 'user_999')
@ -214,7 +196,7 @@ class Test3LeggedOAuthFlow(TestCase):
url, auth_header = self._oauth_request_info(
url, client_key=self.access.key, client_secret=self.access.secret,
resource_owner_key=t.key, resource_owner_secret=t.secret)
auth = authentication.OAuthAuthentication()
auth = authentication.RestOAuthAuthentication()
req = RequestFactory().get(
url, HTTP_HOST='testserver',
HTTP_AUTHORIZATION=auth_header)
@ -228,11 +210,11 @@ class Test3LeggedOAuthFlow(TestCase):
url, client_key=self.access.key,
client_secret=self.access.secret, resource_owner_key=generate(),
resource_owner_secret=generate())
auth = authentication.OAuthAuthentication()
auth = authentication.RestOAuthAuthentication()
req = RequestFactory().get(
url, HTTP_HOST='testserver',
HTTP_AUTHORIZATION=auth_header)
eq_(auth.is_authenticated(req).status_code, 401)
assert not auth.is_authenticated(req)
def test_get_authorize_page(self):
t = Token.generate_new(REQUEST_TOKEN, self.access)

Просмотреть файл

@ -9,54 +9,15 @@ from django.utils.http import urlencode
import mock
from nose.tools import eq_, ok_
from rest_framework.serializers import ValidationError
from rest_framework.serializers import Serializer, ValidationError
from simplejson import JSONDecodeError
from tastypie.exceptions import UnsupportedFormat
from test_utils import RequestFactory
from mkt.api.exceptions import DeserializationError
from mkt.api.serializers import (PotatoCaptchaSerializer, Serializer,
URLSerializerMixin)
from mkt.api.serializers import PotatoCaptchaSerializer, URLSerializerMixin
from mkt.site.fixtures import fixture
from mkt.site.tests.test_forms import PotatoCaptchaTestCase
class TestSerializer(TestCase):
def setUp(self):
self.s = Serializer()
def test_json(self):
eq_(self.s.deserialize(json.dumps({'foo': 'bar'}),
'application/json'),
{'foo': 'bar'})
def test_decimal(self):
eq_(self.s.serialize({'foo': Decimal('5.00')}),
json.dumps({'foo': '5.00'}))
def test_url(self):
eq_(self.s.deserialize(urlencode({'foo': 'bar'}),
'application/x-www-form-urlencoded'),
{'foo': 'bar'})
eq_(self.s.deserialize(urlencode({'foo': u'baré'}),
'application/x-www-form-urlencoded'),
{'foo': u'baré'})
def test_from_url(self):
with self.assertRaises(UnsupportedFormat):
self.s.to_urlencode({})
def test_deserialization_error(self):
try:
self.s.deserialize('')
except DeserializationError, e:
self.assertIsInstance(e.original, JSONDecodeError)
else:
self.fail('DeserializationError not raised')
class TestPotatoCaptchaSerializer(PotatoCaptchaTestCase):
fixtures = fixture('user_999')

Просмотреть файл

@ -1,39 +0,0 @@
from django.test.client import RequestFactory
from mock import patch
from tastypie.exceptions import ImmediateHttpResponse
from mkt.api.base import HttpTooManyRequests, MarketplaceResource
from mkt.api.tests.test_oauth import BaseOAuth
class ThrottleTests(object):
"""
Mixin to add tests that ensure API endpoints are being appropriately
throttled.
Note: subclasses will need to define the resource being tested.
"""
resource = None
request = RequestFactory().post('/')
def test_should_throttle(self):
if not self.resource:
return
with patch.object(self.resource._meta, 'throttle') as throttle:
throttle.should_be_throttled.return_value = True
with self.assertImmediate(HttpTooManyRequests):
self.resource.throttle_check(self.request)
def test_shouldnt_throttle(self):
with patch.object(self, 'resource') as resource:
resource._meta.throttle.should_be_throttled.return_value = False
try:
self.resource.throttle_check(self.request)
except ImmediateHttpResponse:
self.fail('Unthrottled request raises ImmediateHttpResponse')
class TestThrottle(ThrottleTests, BaseOAuth):
resource = MarketplaceResource()

Просмотреть файл

@ -4,6 +4,7 @@ from django.conf import settings
from django.db import models
import amo.models
import mkt.carriers
import mkt.regions
from addons.models import Addon, Category, clean_slug
from amo.decorators import use_master

Просмотреть файл

@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
from nose.tools import eq_, ok_
from rest_framework import serializers
from tastypie.bundle import Bundle
from test_utils import RequestFactory
import amo

Просмотреть файл

@ -23,7 +23,7 @@ from mkt.api.authorization import (AllowAppOwner, GroupPermission,
switch)
from mkt.api.authentication import (RestOAuthAuthentication,
RestSharedSecretAuthentication)
from mkt.api.base import AppViewSet, MarketplaceView
from mkt.api.base import MarketplaceView
from mkt.constants.payments import PAYMENT_STATUSES
from mkt.developers.forms_payments import (BangoPaymentAccountForm,
PaymentCheckForm)
@ -37,6 +37,23 @@ from lib.pay_server import get_client
log = commonware.log.getLogger('z.api.payments')
class PaymentAppViewSet(GenericViewSet):
def initialize_request(self, request, *args, **kwargs):
"""
Pass the value in the URL through to the form defined on the
ViewSet, which will populate the app property with the app object.
You must define a form which will take an app object.
"""
request = (super(PaymentAppViewSet, self)
.initialize_request(request, *args, **kwargs))
self.app = None
form = self.form({'app': kwargs.get('pk')})
if form.is_valid():
self.app = form.cleaned_data['app']
return request
class PaymentAccountSerializer(Serializer):
"""
@ -249,7 +266,7 @@ class AddonPaymentAccountViewSet(CreateModelMixin, RetrieveModelMixin,
obj.save()
class PaymentCheckViewSet(AppViewSet):
class PaymentCheckViewSet(PaymentAppViewSet):
permission_classes = (AllowAppOwner,)
form = PaymentCheckForm
@ -277,7 +294,7 @@ class PaymentCheckViewSet(AppViewSet):
return Response(filtered, status=200)
class PaymentDebugViewSet(AppViewSet):
class PaymentDebugViewSet(PaymentAppViewSet):
permission_classes = [GroupPermission('Transaction', 'Debug')]
form = PaymentCheckForm
@ -293,3 +310,8 @@ class PaymentDebugViewSet(AppViewSet):
'bango': res['bango'],
}
return Response(filtered, status=200)

Просмотреть файл

@ -2,17 +2,22 @@ import json
from django.core.urlresolvers import reverse
from django import forms
from curling.lib import HttpClientError, HttpServerError
from mock import Mock, patch
from nose.tools import eq_, ok_
from test_utils import RequestFactory
import amo
from addons.models import AddonUpsell, AddonUser
from amo.tests import app_factory, TestCase
from market.models import AddonPremium, Price
from mkt.api.tests.test_oauth import RestOAuth
from mkt.developers.api_payments import AddonPaymentAccountSerializer
from mkt.developers.api_payments import (AddonPaymentAccountSerializer,
PaymentAppViewSet)
from mkt.developers.models import (AddonPaymentAccount, PaymentAccount,
SolitudeSeller)
from mkt.developers.tests.test_providers import Patcher
@ -465,3 +470,28 @@ class TestPaymentDebug(AccountCase, RestOAuth):
res = self.client.get(self.list_url)
eq_(res.status_code, 200)
eq_(res.json['bango']['environment'], 'dev')
class Form(forms.Form):
app = forms.ChoiceField(choices=(('valid', 'valid'),))
class TestPaymentAppViewSet(TestCase):
def setUp(self):
self.request = RequestFactory().get('/')
self.viewset = PaymentAppViewSet()
self.viewset.action_map = {}
self.viewset.form = Form
def test_ok(self):
self.viewset.initialize_request(self.request, pk='valid')
ok_(self.viewset.app)
def test_not_ok(self):
self.viewset.initialize_request(self.request, pk='invalid')
eq_(self.viewset.app, None)

Просмотреть файл

@ -1,16 +0,0 @@
from mkt.api.base import MarketplaceResource
class RegionResource(MarketplaceResource):
class Meta(MarketplaceResource.Meta):
allowed_methods = []
fields = ('name', 'slug', 'mcc', 'adolescent')
resource_name = 'region'
include_resource_uri = False
def full_dehydrate(self, bundle):
bundle.data = {}
for field in self._meta.fields:
bundle.data[field] = getattr(bundle.obj, field)
return bundle

Просмотреть файл

@ -1,23 +0,0 @@
from nose.tools import eq_
from tastypie.bundle import Bundle
import amo
import amo.tests
from mkt.constants.regions import REGIONS_DICT as regions
from mkt.regions.api import RegionResource
class TestRegionResource(amo.tests.TestCase):
def setUp(self):
self.app = amo.tests.app_factory()
self.resource = RegionResource()
self.bundle = Bundle(obj=regions['us'], request=None)
def test_full_dehydrate(self):
res = self.resource.full_dehydrate(self.bundle)
eq_(res.obj, regions['us'])
for field in self.resource._meta.fields:
eq_(res.data[field], getattr(res.obj, field))

Просмотреть файл

@ -201,7 +201,7 @@ class TestApi(RestOAuth, ESTestCase):
req.amo_user = user
return True
with patch('mkt.api.authentication.SharedSecretAuthentication'
with patch('mkt.api.authentication.RestSharedSecretAuthentication'
'.is_authenticated', fakeauth):
with self.settings(SITE_URL=''):
self.create()
@ -215,7 +215,7 @@ class TestApi(RestOAuth, ESTestCase):
req.user = AnonymousUser()
return True
with patch('mkt.api.authentication.SharedSecretAuthentication'
with patch('mkt.api.authentication.RestSharedSecretAuthentication'
'.is_authenticated', fakeauth):
with self.settings(SITE_URL=''):
self.create()

Просмотреть файл

@ -36,10 +36,6 @@ log = logging.getLogger('z.mkt.site')
# {% csrf_token %}.
@requires_csrf_token
def handler403(request):
# NOTE: The mkt.api uses Tastypie which has its own mechanism for
# triggering 403s. If we ever end up calling PermissionDenied there, we'll
# need something here similar to the 404s and 500s.
#
# TODO: Bug 793241 for different 403 templates at different URL paths.
return jingo.render(request, 'site/403.html', status=403)

Просмотреть файл

@ -15,7 +15,6 @@ from users.models import UserProfile
import mkt
from mkt.regions import REGIONS_CHOICES_ID_DICT
from mkt.regions.api import RegionResource
log = commonware.log.getLogger('z.webapps')
@ -129,11 +128,15 @@ def es_app_to_dict(obj, region=None, profile=None, request=None):
if not data['public_stats']:
data['weekly_downloads'] = None
data['regions'] = RegionResource().dehydrate_objects(
map(REGIONS_CHOICES_ID_DICT.get,
app.get_region_ids(worldwide=True,
excluded=obj.region_exclusions)))
def serialize_region(o):
d = {}
for field in ('name', 'slug', 'mcc', 'adolescent'):
d[field] = getattr(o, field, None)
return d
data['regions'] = [serialize_region(REGIONS_CHOICES_ID_DICT.get(k))
for k in app.get_region_ids(
worldwide=True,
excluded=obj.region_exclusions)]
if src.get('premium_type') in amo.ADDON_PREMIUMS:
acct = list(AddonPaymentAccount.objects.filter(addon=app))

Просмотреть файл

@ -39,7 +39,6 @@ djangorestframework==2.3.9
#django-session-csrf==0.6
django-statsd-mozilla==0.3.8.6
django-storages==1.1.8
django-tastypie==0.9.11
django-waffle==0.9.1
easy-thumbnails==1.3
elasticutils==0.8.1