Skip to content

Commit

Permalink
Add default throttle rates
Browse files Browse the repository at this point in the history
* Limit GetToken requests to 10/hour
* Limit PasswordResetEmail requests to 3/hour
  • Loading branch information
LilyFoote committed Jun 20, 2014
1 parent 8c15f3b commit 2be9613
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 10 deletions.
9 changes: 7 additions & 2 deletions README.md
Expand Up @@ -138,8 +138,13 @@ with a selection from
The `/auth/` and `/auth/password_reset/` URLs are protected against throttling
using the built-in [DRF throttle module](http://www.django-rest-framework.org/api-guide/throttling).

You need to set the throttling rates in `DEFAULT_THROTTLE_RATES` setting for
`REST_FRAMEWORK` in your `settings.py`:
The default throttle rates are:

'logins': '10/hour'
'passwords': '3/hour'

You can customise the throttling rates by setting `DEFAULT_THROTTLE_RATES`
in your `settings.py`:

DEFAULT_THROTTLE_RATES = {
'logins': '100/day',
Expand Down
36 changes: 36 additions & 0 deletions user_management/api/tests/test_views.py
Expand Up @@ -65,6 +65,24 @@ def test_delete_no_token(self):
response = self.view_class.as_view()(request)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_default_user_auth_throttle(self):
default_rate = 10
auth_url = reverse('user_management_api:auth')
expected_status = status.HTTP_429_TOO_MANY_REQUESTS

request = APIRequestFactory().get(auth_url)
view = self.view_class.as_view()

# make all but one of our allowed requests
for i in range(default_rate - 1):
view(request)

response = view(request) # our last allowed request
self.assertNotEqual(response.status_code, expected_status)

response = view(request) # our throttled request
self.assertEqual(response.status_code, expected_status)

@patch('rest_framework.throttling.ScopedRateThrottle.THROTTLE_RATES', new={
'logins': '1/minute',
})
Expand Down Expand Up @@ -252,6 +270,24 @@ def test_options(self):
expected_post_options,
)

def test_default_user_password_reset_throttle(self):
default_rate = 3
auth_url = reverse('user_management_api:password_reset')
expected_status = status.HTTP_429_TOO_MANY_REQUESTS

request = APIRequestFactory().get(auth_url)
view = self.view_class.as_view()

# make all but one of our allowed requests
for i in range(default_rate - 1):
view(request)

response = view(request) # our last allowed request
self.assertNotEqual(response.status_code, expected_status)

response = view(request) # our throttled request
self.assertEqual(response.status_code, expected_status)

@patch('rest_framework.throttling.ScopedRateThrottle.THROTTLE_RATES', new={
'passwords': '1/minute',
})
Expand Down
17 changes: 17 additions & 0 deletions user_management/api/throttling.py
@@ -0,0 +1,17 @@
from rest_framework.throttling import ScopedRateThrottle


class DefaultRateMixin(object):
def get_rate(self):
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
return self.default_rate


class LoginRateThrottle(DefaultRateMixin, ScopedRateThrottle):
default_rate = '10/hour'


class PasswordResetRateThrottle(DefaultRateMixin, ScopedRateThrottle):
default_rate = '3/hour'
7 changes: 3 additions & 4 deletions user_management/api/views.py
Expand Up @@ -10,17 +10,16 @@
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.throttling import ScopedRateThrottle

from . import serializers, permissions
from . import permissions, serializers, throttling


User = get_user_model()


class GetToken(ObtainAuthToken):
renderer_classes = (renderers.JSONRenderer, renderers.BrowsableAPIRenderer)
throttle_classes = [ScopedRateThrottle]
throttle_classes = [throttling.LoginRateThrottle]
throttle_scope = 'logins'

def delete(self, request, *args, **kwargs):
Expand Down Expand Up @@ -70,7 +69,7 @@ class PasswordResetEmail(generics.GenericAPIView):
permission_classes = [permissions.IsNotAuthenticated]
template_name = 'user_management/password_reset_email.html'
serializer_class = serializers.PasswordResetEmailSerializer
throttle_classes = [ScopedRateThrottle]
throttle_classes = [throttling.PasswordResetRateThrottle]
throttle_scope = 'passwords'

def email_context(self, site, user):
Expand Down
4 changes: 0 additions & 4 deletions user_management/tests/run.py
Expand Up @@ -39,10 +39,6 @@
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_THROTTLE_RATES': {
'logins': '100/day',
'passwords': '100/day',
}
},
)

Expand Down

0 comments on commit 2be9613

Please sign in to comment.