2011-05-19 21:36:30 +04:00
|
|
|
"""
|
2011-12-09 16:35:42 +04:00
|
|
|
The :mod:`permissions` module bundles a set of permission classes that are used
|
|
|
|
for checking if a request passes a certain set of constraints. You can assign a permission
|
2011-05-19 21:36:30 +04:00
|
|
|
class to your view by setting your View's :attr:`permissions` class attribute.
|
|
|
|
"""
|
|
|
|
|
2011-04-27 21:08:32 +04:00
|
|
|
from django.core.cache import cache
|
|
|
|
from djangorestframework import status
|
2011-05-10 13:49:28 +04:00
|
|
|
from djangorestframework.response import ErrorResponse
|
2011-04-27 21:08:32 +04:00
|
|
|
import time
|
|
|
|
|
2011-05-10 13:49:28 +04:00
|
|
|
__all__ = (
|
|
|
|
'BasePermission',
|
|
|
|
'FullAnonAccess',
|
|
|
|
'IsAuthenticated',
|
|
|
|
'IsAdminUser',
|
|
|
|
'IsUserOrIsAnonReadOnly',
|
2011-06-14 13:31:18 +04:00
|
|
|
'PerUserThrottling',
|
|
|
|
'PerViewThrottling',
|
2011-06-14 13:30:29 +04:00
|
|
|
'PerResourceThrottling'
|
2011-05-10 13:49:28 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
_403_FORBIDDEN_RESPONSE = ErrorResponse(
|
|
|
|
status.HTTP_403_FORBIDDEN,
|
|
|
|
{'detail': 'You do not have permission to access this resource. ' +
|
|
|
|
'You may need to login or otherwise authenticate the request.'})
|
|
|
|
|
2011-06-14 14:08:29 +04:00
|
|
|
_503_SERVICE_UNAVAILABLE = ErrorResponse(
|
2011-05-10 13:49:28 +04:00
|
|
|
status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
|
|
{'detail': 'request was throttled'})
|
|
|
|
|
|
|
|
|
2011-04-27 21:08:32 +04:00
|
|
|
class BasePermission(object):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
A base class from which all permission classes should inherit.
|
|
|
|
"""
|
2011-04-27 21:08:32 +04:00
|
|
|
def __init__(self, view):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
Permission classes are always passed the current view on creation.
|
|
|
|
"""
|
2011-04-27 21:08:32 +04:00
|
|
|
self.view = view
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-05-10 13:49:28 +04:00
|
|
|
def check_permission(self, auth):
|
|
|
|
"""
|
2011-05-19 21:36:30 +04:00
|
|
|
Should simply return, or raise an :exc:`response.ErrorResponse`.
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
pass
|
2011-04-27 21:08:32 +04:00
|
|
|
|
2011-05-04 12:21:17 +04:00
|
|
|
|
|
|
|
class FullAnonAccess(BasePermission):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
Allows full access.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def check_permission(self, user):
|
|
|
|
pass
|
|
|
|
|
2011-05-04 12:21:17 +04:00
|
|
|
|
2011-04-27 21:08:32 +04:00
|
|
|
class IsAuthenticated(BasePermission):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
Allows access only to authenticated users.
|
|
|
|
"""
|
2011-04-27 21:08:32 +04:00
|
|
|
|
2011-05-10 13:49:28 +04:00
|
|
|
def check_permission(self, user):
|
|
|
|
if not user.is_authenticated():
|
2011-12-09 16:35:42 +04:00
|
|
|
raise _403_FORBIDDEN_RESPONSE
|
2011-04-27 21:08:32 +04:00
|
|
|
|
2011-05-19 11:36:55 +04:00
|
|
|
|
2011-05-19 00:13:48 +04:00
|
|
|
class IsAdminUser(BasePermission):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
|
|
|
Allows access only to admin users.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def check_permission(self, user):
|
2011-08-24 14:49:16 +04:00
|
|
|
if not user.is_staff:
|
2011-05-10 13:49:28 +04:00
|
|
|
raise _403_FORBIDDEN_RESPONSE
|
|
|
|
|
|
|
|
|
|
|
|
class IsUserOrIsAnonReadOnly(BasePermission):
|
|
|
|
"""
|
|
|
|
The request is authenticated as a user, or is a read-only request.
|
|
|
|
"""
|
|
|
|
|
2011-12-09 16:35:42 +04:00
|
|
|
def check_permission(self, user):
|
2011-05-10 13:49:28 +04:00
|
|
|
if (not user.is_authenticated() and
|
|
|
|
self.view.method != 'GET' and
|
|
|
|
self.view.method != 'HEAD'):
|
|
|
|
raise _403_FORBIDDEN_RESPONSE
|
|
|
|
|
2011-06-14 14:08:29 +04:00
|
|
|
|
2012-02-11 17:00:38 +04:00
|
|
|
class DjangoModelPermission(BasePermission):
|
2012-02-11 04:49:28 +04:00
|
|
|
"""
|
2012-02-11 05:02:42 +04:00
|
|
|
The request is authenticated against the Django user's permissions on the
|
2012-02-11 17:00:38 +04:00
|
|
|
`Resource`'s `Model`.
|
2012-02-11 04:49:28 +04:00
|
|
|
|
2012-02-11 17:00:38 +04:00
|
|
|
This permission should only be used on views with a `ModelResource`.
|
|
|
|
"""
|
2012-02-11 04:54:28 +04:00
|
|
|
|
2012-02-11 17:00:38 +04:00
|
|
|
# Map methods into required permission codes.
|
|
|
|
# Override this if you need to also provide 'read' permissions,
|
|
|
|
# or other custom behaviour.
|
|
|
|
perms_map = {
|
|
|
|
'GET': [],
|
|
|
|
'OPTIONS': [],
|
|
|
|
'HEAD': [],
|
|
|
|
'POST': ['%(app_label)s.add_%(model_name)s'],
|
|
|
|
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
|
|
|
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
|
|
|
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
|
|
|
}
|
|
|
|
|
|
|
|
def get_required_permissions(self, method, model_cls):
|
|
|
|
"""
|
|
|
|
Given a model and an HTTP method, return the list of permission
|
|
|
|
codes that the user is required to have.
|
|
|
|
"""
|
|
|
|
kwargs = {
|
|
|
|
'app_label': model_cls._meta.app_label,
|
|
|
|
'model_name': model_cls.__name__.lower()
|
2012-02-11 04:49:28 +04:00
|
|
|
}
|
2012-02-11 17:00:38 +04:00
|
|
|
try:
|
|
|
|
return [perm % kwargs for perm in self.perms_map[method]]
|
|
|
|
except KeyError:
|
|
|
|
ErrorResponse(status.HTTP_405_METHOD_NOT_ALLOWED)
|
2012-02-11 04:49:28 +04:00
|
|
|
|
2012-02-11 17:00:38 +04:00
|
|
|
def check_permission(self, user):
|
|
|
|
method = self.view.method
|
|
|
|
model_cls = self.view.resource.model
|
|
|
|
perms = self.get_required_permissions(method, model_cls)
|
2012-02-11 04:49:28 +04:00
|
|
|
|
2012-02-11 17:00:38 +04:00
|
|
|
if not user.has_perms(perms):
|
2012-02-11 04:49:28 +04:00
|
|
|
raise _403_FORBIDDEN_RESPONSE
|
|
|
|
|
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
class BaseThrottle(BasePermission):
|
2011-05-10 13:49:28 +04:00
|
|
|
"""
|
2011-06-11 22:34:54 +04:00
|
|
|
Rate throttling of requests.
|
2011-05-10 13:49:28 +04:00
|
|
|
|
2011-06-14 14:08:29 +04:00
|
|
|
The rate (requests / seconds) is set by a :attr:`throttle` attribute
|
|
|
|
on the :class:`.View` class. The attribute is a string of the form 'number of
|
|
|
|
requests/period'.
|
|
|
|
|
|
|
|
Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
|
2011-04-27 21:08:32 +04:00
|
|
|
|
|
|
|
Previous request information used for throttling is stored in the cache.
|
2011-12-09 16:35:42 +04:00
|
|
|
"""
|
2011-05-10 13:49:28 +04:00
|
|
|
|
2011-06-14 14:08:29 +04:00
|
|
|
attr_name = 'throttle'
|
|
|
|
default = '0/sec'
|
|
|
|
timer = time.time
|
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def get_cache_key(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
"""
|
|
|
|
Should return a unique cache-key which can be used for throttling.
|
2011-12-09 17:39:56 +04:00
|
|
|
Must be overridden.
|
2011-06-11 22:34:54 +04:00
|
|
|
"""
|
|
|
|
pass
|
2011-04-27 21:08:32 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def check_permission(self, auth):
|
2011-06-14 14:08:29 +04:00
|
|
|
"""
|
|
|
|
Check the throttling.
|
|
|
|
Return `None` or raise an :exc:`.ErrorResponse`.
|
|
|
|
"""
|
|
|
|
num, period = getattr(self.view, self.attr_name, self.default).split('/')
|
2011-06-11 22:34:54 +04:00
|
|
|
self.num_requests = int(num)
|
|
|
|
self.duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
|
|
|
|
self.auth = auth
|
|
|
|
self.check_throttle()
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def check_throttle(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
"""
|
|
|
|
Implement the check to see if the request should be throttled.
|
|
|
|
|
|
|
|
On success calls :meth:`throttle_success`.
|
|
|
|
On failure calls :meth:`throttle_failure`.
|
|
|
|
"""
|
2011-06-11 22:34:54 +04:00
|
|
|
self.key = self.get_cache_key()
|
|
|
|
self.history = cache.get(self.key, [])
|
2011-06-14 14:08:29 +04:00
|
|
|
self.now = self.timer()
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-06-14 14:08:29 +04:00
|
|
|
# Drop any requests from the history which have now passed the
|
|
|
|
# throttle duration
|
2011-06-15 17:41:09 +04:00
|
|
|
while self.history and self.history[-1] <= self.now - self.duration:
|
2011-06-11 22:34:54 +04:00
|
|
|
self.history.pop()
|
|
|
|
if len(self.history) >= self.num_requests:
|
|
|
|
self.throttle_failure()
|
|
|
|
else:
|
|
|
|
self.throttle_success()
|
2011-06-13 22:42:37 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def throttle_success(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
"""
|
|
|
|
Inserts the current request's timestamp along with the key
|
|
|
|
into the cache.
|
|
|
|
"""
|
2011-06-11 22:34:54 +04:00
|
|
|
self.history.insert(0, self.now)
|
|
|
|
cache.set(self.key, self.history, self.duration)
|
2011-06-15 17:41:09 +04:00
|
|
|
header = 'status=SUCCESS; next=%s sec' % self.next()
|
|
|
|
self.view.add_header('X-Throttle', header)
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def throttle_failure(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
"""
|
|
|
|
Called when a request to the API has failed due to throttling.
|
|
|
|
Raises a '503 service unavailable' response.
|
|
|
|
"""
|
2011-06-15 17:41:09 +04:00
|
|
|
header = 'status=FAILURE; next=%s sec' % self.next()
|
|
|
|
self.view.add_header('X-Throttle', header)
|
2011-06-14 14:08:29 +04:00
|
|
|
raise _503_SERVICE_UNAVAILABLE
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-06-13 22:42:37 +04:00
|
|
|
def next(self):
|
|
|
|
"""
|
|
|
|
Returns the recommended next request time in seconds.
|
|
|
|
"""
|
2011-06-15 17:41:09 +04:00
|
|
|
if self.history:
|
|
|
|
remaining_duration = self.duration - (self.now - self.history[-1])
|
|
|
|
else:
|
|
|
|
remaining_duration = self.duration
|
|
|
|
|
|
|
|
available_requests = self.num_requests - len(self.history) + 1
|
|
|
|
|
|
|
|
return '%.2f' % (remaining_duration / float(available_requests))
|
2011-06-14 14:08:29 +04:00
|
|
|
|
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
class PerUserThrottling(BaseThrottle):
|
2011-06-11 05:16:35 +04:00
|
|
|
"""
|
2011-06-14 14:08:29 +04:00
|
|
|
Limits the rate of API calls that may be made by a given user.
|
|
|
|
|
|
|
|
The user 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.
|
2011-06-11 05:16:35 +04:00
|
|
|
"""
|
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def get_cache_key(self):
|
|
|
|
if self.auth.is_authenticated():
|
2012-01-30 19:54:38 +04:00
|
|
|
ident = self.auth.id
|
2011-06-11 22:34:54 +04:00
|
|
|
else:
|
|
|
|
ident = self.view.request.META.get('REMOTE_ADDR', None)
|
2011-06-14 14:08:29 +04:00
|
|
|
return 'throttle_user_%s' % ident
|
|
|
|
|
2011-06-11 05:16:35 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
class PerViewThrottling(BaseThrottle):
|
|
|
|
"""
|
2011-06-14 14:08:29 +04:00
|
|
|
Limits the rate of API calls that may be used on a given view.
|
|
|
|
|
|
|
|
The class name of the view is used as a unique identifier to
|
|
|
|
throttle against.
|
2011-06-11 22:34:54 +04:00
|
|
|
"""
|
|
|
|
|
|
|
|
def get_cache_key(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
return 'throttle_view_%s' % self.view.__class__.__name__
|
|
|
|
|
2011-12-09 16:35:42 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
class PerResourceThrottling(BaseThrottle):
|
|
|
|
"""
|
2011-06-14 14:08:29 +04:00
|
|
|
Limits the rate of API calls that may be used against all views on
|
|
|
|
a given resource.
|
|
|
|
|
|
|
|
The class name of the resource is used as a unique identifier to
|
|
|
|
throttle against.
|
2011-06-11 22:34:54 +04:00
|
|
|
"""
|
2011-06-11 05:16:35 +04:00
|
|
|
|
2011-06-11 22:34:54 +04:00
|
|
|
def get_cache_key(self):
|
2011-06-14 14:08:29 +04:00
|
|
|
return 'throttle_resource_%s' % self.view.resource.__class__.__name__
|