Allow anonymous GET requests to ratings API (bug 858324)

This commit is contained in:
Chuck Harmston 2013-04-05 12:06:54 -05:00
Родитель 4e3e334c3e
Коммит d400523a85
6 изменённых файлов: 77 добавлений и 13 удалений

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

@ -7,8 +7,6 @@ Ratings API
These endpoints allow the retrieval, creation, and modification of ratings on These endpoints allow the retrieval, creation, and modification of ratings on
apps in Marketplace. apps in Marketplace.
.. note:: All ratings methods require authentication.
_`List` _`List`
======= =======
@ -17,6 +15,8 @@ _`List`
Get a list of ratings from the Marketplace Get a list of ratings from the Marketplace
.. note:: Authentication is optional.
**Request**: **Request**:
:query app: the ID or slug of the app whose ratings are to be returned. :query app: the ID or slug of the app whose ratings are to be returned.
@ -67,6 +67,8 @@ _`Detail`
Get a single rating from the Marketplace using its `resource_uri` from the Get a single rating from the Marketplace using its `resource_uri` from the
`List`_. `List`_.
.. note:: Authentication is optional.
**Response**: **Response**:
.. code-block:: json .. code-block:: json
@ -93,6 +95,8 @@ _`Create`
Create a rating. Create a rating.
.. note:: Authentication required.
**Request**: **Request**:
:param app: the ID of the app being reviewed :param app: the ID of the app being reviewed
@ -137,6 +141,8 @@ _`Update`
Update a rating from the Marketplace using its `resource_uri` from the Update a rating from the Marketplace using its `resource_uri` from the
`List`_. `List`_.
.. note:: Authentication required.
**Request**: **Request**:
:param body: text of the rating :param body: text of the rating
@ -174,6 +180,8 @@ _`Delete`
Delete a rating from the Marketplace using its `resource_uri` from the Delete a rating from the Marketplace using its `resource_uri` from the
`List`_. `List`_.
.. note:: Authentication required.
**Response**: **Response**:
:status 204: successfully deleted. :status 204: successfully deleted.

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

@ -137,6 +137,22 @@ class OAuthAuthentication(Authentication):
return True return True
class SelectiveAuthentication(Authentication):
"""
Authenticate all requests using verbs passed as positional arguments to the
constructor. Example usage:
class Meta:
authentication = SelectiveAuthentication('GET', 'POST')
"""
def __init__(self, *args):
self.skip_authentication = args
super(SelectiveAuthentication, self).__init__()
def is_authenticated(self, request, **kwargs):
return request.method in self.skip_authentication
class OptionalOAuthAuthentication(OAuthAuthentication): class OptionalOAuthAuthentication(OAuthAuthentication):
""" """
Like OAuthAuthentication, but doesn't require there to be Like OAuthAuthentication, but doesn't require there to be

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

@ -101,18 +101,19 @@ class Marketplace(object):
of Authentication methods. If so it will go through in order, when one of Authentication methods. If so it will go through in order, when one
passes, it will use that. passes, it will use that.
Any authentication method can still return a HttpResponse to break out If authentication backends return a response (e.g. DigestAuth), it will
of the loop if they desire. be squashed. To get around this, raise an ImmediateHttpResponse with the
desired response.
""" """
for auth in self._auths(): for auth in self._auths():
auth_result = auth.is_authenticated(request) auth_result = auth.is_authenticated(request)
if isinstance(auth_result, http.HttpResponse): if isinstance(auth_result, http.HttpResponse):
raise ImmediateHttpResponse(response=auth_result) return False
if auth_result: if auth_result:
log.info('Logged in using %s' % auth.__class__.__name__) log.info('Logged in using %s' % auth.__class__.__name__)
return return True
raise ImmediateHttpResponse(response=http.HttpUnauthorized()) raise ImmediateHttpResponse(response=http.HttpUnauthorized())

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

@ -237,3 +237,20 @@ class TestMultipleAuthentication(TestCase):
eq_(self.resource.is_authenticated(req), None) eq_(self.resource.is_authenticated(req), None)
# This never even got called. # This never even got called.
ok_(not next_auth.is_authenticated.called) ok_(not next_auth.is_authenticated.called)
class TestSelectiveAuthentication(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_single_verb(self):
auth = authentication.SelectiveAuthentication('GET')
eq_(auth.is_authenticated(self.factory.get('/')), True)
eq_(auth.is_authenticated(self.factory.post('/')), False)
def test_multiple_verbs(self):
auth = authentication.SelectiveAuthentication('GET', 'PUT')
eq_(auth.is_authenticated(self.factory.get('/')), True)
eq_(auth.is_authenticated(self.factory.put('/')), True)
eq_(auth.is_authenticated(self.factory.post('/')), False)

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

@ -1,5 +1,6 @@
import commonware.log
from django.conf.urls import url from django.conf.urls import url
import commonware.log
from tastypie import fields, http from tastypie import fields, http
from tastypie.bundle import Bundle from tastypie.bundle import Bundle
from tastypie.authorization import Authorization from tastypie.authorization import Authorization
@ -14,6 +15,7 @@ from mkt.api.authentication import (AppOwnerAuthorization,
OwnerAuthorization, OwnerAuthorization,
OAuthAuthentication, OAuthAuthentication,
PermissionAuthorization, PermissionAuthorization,
SelectiveAuthentication,
SharedSecretAuthentication) SharedSecretAuthentication)
from mkt.api.base import MarketplaceModelResource from mkt.api.base import MarketplaceModelResource
from mkt.api.resources import AppResource, UserResource from mkt.api.resources import AppResource, UserResource
@ -37,7 +39,9 @@ class RatingResource(MarketplaceModelResource):
list_allowed_methods = ['get', 'post'] list_allowed_methods = ['get', 'post']
detail_allowed_methods = ['get', 'put', 'delete'] detail_allowed_methods = ['get', 'put', 'delete']
always_return_data = True always_return_data = True
authentication = (SharedSecretAuthentication(), OAuthAuthentication()) authentication = (SharedSecretAuthentication(),
OAuthAuthentication(),
SelectiveAuthentication('GET'))
authorization = Authorization() authorization = Authorization()
fields = ['rating', 'body'] fields = ['rating', 'body']
@ -151,12 +155,18 @@ class RatingResource(MarketplaceModelResource):
'slug': addon.app_slug 'slug': addon.app_slug
} }
filters = dict(addon=addon, user=request.user) filters = dict(addon=addon)
if addon.is_packaged: if addon.is_packaged:
filters['version'] = addon.current_version filters['version'] = addon.current_version
existing_review = Review.objects.valid().filter(**filters).exists()
if not request.user.is_anonymous():
filters['user'] = request.user
existing_review = Review.objects.valid().filter(**filters)
data['user'] = {'can_rate': not addon.has_author(request.user), data['user'] = {'can_rate': not addon.has_author(request.user),
'has_rated': existing_review} 'has_rated': existing_review.exists()}
else:
data['user'] = None
return data return data
def override_urls(self): def override_urls(self):

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

@ -34,6 +34,18 @@ class TestRatingResource(BaseOAuth, AMOPaths):
assert not data['user']['can_rate'] assert not data['user']['can_rate']
assert not data['user']['has_rated'] assert not data['user']['has_rated']
def test_anonymous_get_list(self):
res = self.anon.get(list_url('rating'))
data = json.loads(res.content)
eq_(res.status_code, 200)
assert 'user' not in data
def test_anonymous_get_detail(self):
res = self.anon.get(self.collection_url)
data = json.loads(res.content)
eq_(res.status_code, 200)
eq_(data['user'], None)
def test_non_owner(self): def test_non_owner(self):
res = self.client.get(self.collection_url) res = self.client.get(self.collection_url)
data = json.loads(res.content) data = json.loads(res.content)