From 659898ffaf24f74b62e73c487cd81bad21904790 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 27 Apr 2011 18:08:32 +0100 Subject: [PATCH] Inital pass at generic permissions, throttling etc. --- djangorestframework/permissions.py | 74 +++++++++++++++++++++++++ djangorestframework/tests/throttling.py | 38 +++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 djangorestframework/permissions.py create mode 100644 djangorestframework/tests/throttling.py diff --git a/djangorestframework/permissions.py b/djangorestframework/permissions.py new file mode 100644 index 000000000..98d4b0be3 --- /dev/null +++ b/djangorestframework/permissions.py @@ -0,0 +1,74 @@ +from django.core.cache import cache +from djangorestframework import status +import time + + +class BasePermission(object): + """A base class from which all permission classes should inherit.""" + def __init__(self, view): + self.view = view + + def has_permission(self, auth): + return True + +class IsAuthenticated(BasePermission): + """""" + def has_permission(self, auth): + return auth is not None and auth.is_authenticated() + +#class IsUser(BasePermission): +# """The request has authenticated as a user.""" +# def has_permission(self, auth): +# pass +# +#class IsAdminUser(): +# """The request has authenticated as an admin user.""" +# def has_permission(self, auth): +# pass +# +#class IsUserOrIsAnonReadOnly(BasePermission): +# """The request has authenticated as a user, or is a read-only request.""" +# def has_permission(self, auth): +# pass +# +#class OAuthTokenInScope(BasePermission): +# def has_permission(self, auth): +# pass +# +#class UserHasModelPermissions(BasePermission): +# def has_permission(self, auth): +# pass + + +class Throttling(BasePermission): + """Rate throttling of requests on a per-user basis. + + The rate is set by a 'throttle' attribute on the view class. + The attribute is a two tuple of the form (number of requests, duration in seconds). + + The user's id will be used as a unique identifier if the user is authenticated. + For anonymous requests, the IP address of the client will be used. + + Previous request information used for throttling is stored in the cache. + """ + def has_permission(self, auth): + (num_requests, duration) = getattr(self.view, 'throttle', (0, 0)) + + if auth.is_authenticated(): + ident = str(auth) + else: + ident = self.view.request.META.get('REMOTE_ADDR', None) + + key = 'throttle_%s' % ident + history = cache.get(key, []) + now = time.time() + + # Drop any requests from the history which have now passed the throttle duration + while history and history[0] < now - duration: + history.pop() + + if len(history) >= num_requests: + raise ErrorResponse(status.HTTP_503_SERVICE_UNAVAILABLE, {'detail': 'request was throttled'}) + + history.insert(0, now) + cache.set(key, history, duration) diff --git a/djangorestframework/tests/throttling.py b/djangorestframework/tests/throttling.py new file mode 100644 index 000000000..46383271f --- /dev/null +++ b/djangorestframework/tests/throttling.py @@ -0,0 +1,38 @@ +from django.conf.urls.defaults import patterns +from django.test import TestCase +from django.utils import simplejson as json + +from djangorestframework.compat import RequestFactory +from djangorestframework.resource import Resource +from djangorestframework.permissions import Throttling + + +class MockResource(Resource): + permissions = ( Throttling, ) + throttle = (3, 1) # 3 requests per second + + def get(self, request): + return 'foo' + +urlpatterns = patterns('', + (r'^$', MockResource.as_view()), +) + + +#class ThrottlingTests(TestCase): +# """Basic authentication""" +# urls = 'djangorestframework.tests.throttling' +# +# def test_requests_are_throttled(self): +# """Ensure request rate is limited""" +# for dummy in range(3): +# response = self.client.get('/') +# response = self.client.get('/') +# +# def test_request_throttling_is_per_user(self): +# """Ensure request rate is only limited per user, not globally""" +# pass +# +# def test_request_throttling_expires(self): +# """Ensure request rate is limited for a limited duration only""" +# pass