i use django-brake
and memcached
@ratelimit(field = 'email', method = 'POST', rate = '5/m') @ratelimit(field = 'email', method = 'POST', rate = '10/h') @ratelimit(field = 'email', method = 'POST', rate = '20/d') def login_failure(request, login_form): "" " Increment cache counters, 403 if over limit. "" " was_limited = getattr(request, 'limited', False) if was_limited: limits = getattr(request, 'limits', []) login_form.full_clean() login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList())\ .append('accout locked, try ' + str(limits[0]['period']) + ' seconds later') return render(request, 'user/login.html', { 'form': login_form }) def login(request): if request.method == 'GET': next = request.GET.get('next', '') return render(request, 'user/login.html', { 'next': next }) elif request.method == 'POST': login_form = LoginForm(request.POST) # check first from brake.utils import get_limits limits = get_limits(request, 'login_failure', 'email', [60, 3600, 86400]) if limits: login_form.full_clean() login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList())\ .append('accout locked, try ' + str(limits[0]['period']) + ' seconds later') return render(request, 'user/login.html', { 'form': login_form }) if login_form.is_valid(): email = login_form.cleaned_data['email'] submit_pwd = login_form.cleaned_data['password'] user = authenticate(username = email, password = submit_pwd) if user is None: # res = login_failure(request, login_form) if res is None: login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList()).append('password wrong') res = render(request, 'user/login.html', { 'form': login_form }) return res ... login etc... else: ...
You could save a session if the user has failed to login.
request.SESSION['login_tries'] = 1
and if they fail to login again
request.SESSioN['login_tries'] = 2
There's actually a project out there which provides a Django middleware to do just this, called django-axes. Simply install it with the instructions provided and then set AXES_LOGIN_FAILURE_LIMIT to the number of login attempts you want before a record is created for the failed logins. You'll still have to check this record when you want to lock someone out, however.,How to display the last login date and time on the users list on the django admin site?,If the session becomes equal to the amount of login tries you want them tho have, then do something. ,How to limit the number of entries in django database ? Using the default database (sqlite3)
You could save a session if the user has failed to login.
request.SESSION['login_tries'] = 1
and if they fail to login again
request.SESSioN['login_tries'] = 2
i use django-brake
and memcached
@ratelimit(field = 'email', method = 'POST', rate = '5/m') @ratelimit(field = 'email', method = 'POST', rate = '10/h') @ratelimit(field = 'email', method = 'POST', rate = '20/d') def login_failure(request, login_form): "" " Increment cache counters, 403 if over limit. "" " was_limited = getattr(request, 'limited', False) if was_limited: limits = getattr(request, 'limits', []) login_form.full_clean() login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList())\ .append('accout locked, try ' + str(limits[0]['period']) + ' seconds later') return render(request, 'user/login.html', { 'form': login_form }) def login(request): if request.method == 'GET': next = request.GET.get('next', '') return render(request, 'user/login.html', { 'next': next }) elif request.method == 'POST': login_form = LoginForm(request.POST) # check first from brake.utils import get_limits limits = get_limits(request, 'login_failure', 'email', [60, 3600, 86400]) if limits: login_form.full_clean() login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList())\ .append('accout locked, try ' + str(limits[0]['period']) + ' seconds later') return render(request, 'user/login.html', { 'form': login_form }) if login_form.is_valid(): email = login_form.cleaned_data['email'] submit_pwd = login_form.cleaned_data['password'] user = authenticate(username = email, password = submit_pwd) if user is None: # res = login_failure(request, login_form) if res is None: login_form._errors.setdefault(NON_FIELD_ERRORS, ErrorList()).append('password wrong') res = render(request, 'user/login.html', { 'form': login_form }) return res ... login etc... else: ...
django-axes is a very simple way for you to keep track of failed login attempts, both for the Django admin and for the rest of your site. The name is sort of a geeky pun, since axes can be read interpreted as:,Keep track of failed login attempts in Django-powered sites.,Using django-axes is extremely simple. Once you install the application and the middleware, all you need to do is periodically check the Access Attempts section of the admin.,By default, django-axes will lock out repeated attempts from the same IP address. You can allow this IP to attempt again by deleting the relevant AccessAttempt records in the admin.
You can install the latest stable package running this command:
$ pip install django - axes
Also you can install the development version running this command:
$ pip install - e git + http: //github.com/django-pci/django-axes.git#egg=django_axes-dev
Tests can be run, after you clone the repository and having django installed, like:
$ PYTHONPATH = $PYTHONPATH: $PWD django - admin.py test axes--settings = axes.test_settings
First of all, you must add this project to your list of INSTALLED_APPS in settings.py:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
...
'axes',
...
)
Next, install the FailedLoginMiddleware middleware:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'axes.middleware.FailedLoginMiddleware'
)
You may find that Axes is not capturing your failed login attempts. It may be that you need to manually add watch_login to your login url. For example, in your urls.py:
...
from django.contrib.auth.views
import login, logout, password_change
from axes.decorators
import watch_login
...
urlpatterns = patterns('',
(r '^login/$', watch_login(login), {
'template_name': 'auth/login.html'
}),
...
AnonRateThrottle is suitable if you want to restrict the rate of requests from unknown sources.,UserRateThrottle is suitable if you want simple global rate restrictions per-user.,If your project relies on guaranteeing the number of requests during concurrent requests, you will need to implement your own throttle class. See issue #5181 for more details.,The AnonRateThrottle will only ever throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against.
The default throttling policy may be set globally, using the DEFAULT_THROTTLE_CLASSES
and DEFAULT_THROTTLE_RATES
settings. For example.
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}
You can also set the throttling policy on a per-view or per-viewset basis,
using the APIView
class-based views.
from rest_framework.response
import Response
from rest_framework.throttling
import UserRateThrottle
from rest_framework.views
import APIView
class ExampleView(APIView):
throttle_classes = [UserRateThrottle]
def get(self, request, format = None):
content = {
'status': 'request was permitted'
}
return Response(content)
If you're using the @api_view
decorator with function based views you can use the following decorator.
@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request, format = None):
content = {
'status': 'request was permitted'
}
return Response(content)
If you need to use a cache other than 'default'
, you can do so by creating a custom throttle class and setting the cache
attribute. For example:
from django.core.cache
import caches
class CustomAnonRateThrottle(AnonRateThrottle):
cache = caches['alternate']
For example, multiple user throttle rates could be implemented by using the following classes...
class BurstRateThrottle(UserRateThrottle):
scope = 'burst'
class SustainedRateThrottle(UserRateThrottle):
scope = 'sustained'
If you make a lot of API requests in a short amount of time, you may bump into the API rate limit for requests. When you reach the limit, the Zendesk API stops processing any more requests until a certain amount of time has passed.,For each request, you can check to see if you've bumped into the rate limit. If you get a response code of 429, "Too Many Requests", you've hit the rate limit.,You can also use the following response headers to confirm the account's current rate limit and monitor the number of requests remaining in the current minute:,The 429 status code means too many requests. The Retry-After header specifies that you can retry the API call in 93 seconds. Your code should stop making additional API requests until enough time has passed to retry.
You can also use the following response headers to confirm the account's current rate limit and monitor the number of requests remaining in the current minute:
X - Rate - Limit: 700 X - Rate - Limit - Remaining: 699
X - Rate - Limit: 700 X - Rate - Limit - Remaining: 699
For example, a curl request that bumps into the rate limit might return the following response:
< HTTP / 1.1 429 < Server: nginx / 1.4 .2 < Date: Mon, 04 Nov 2013 00: 18: 27 GMT < Content - Type: text / html; charset = utf - 8 < Content - Length: 85 < Connection: keep - alive < Status: 429 < Cache - Control: no - cache < X - Zendesk - API - Version: v2 < Retry - After: 93 < X - Zendesk - Origin - Server: ** ** . ** ** . ** * . ** ** * .com < X - Zendesk - User - Id: 338231444 < X - Zendesk - Request - Id: c773675d81590abad33i < * Connection #0 to host SUBDOMAIN.zendesk.com left intact* Closing connection # 0 * SSLv3, TLS alert, Client hello(1): Rate limit for ticket updates exceeded, please wait before updating this ticket again
The following pseudo-code shows a simple way to catch rate-limit errors:
response = request.get(url) if response.status equals 429: alert('Rate limited. Waiting to retry…') wait(response.retry - after) retry(url)
response = request.get(url) if response.status equals 429: alert('Rate limited. Waiting to retry…') wait(response.retry - after) retry(url)
X - Rate - Limit: 700 X - Rate - Limit - Remaining: 699
< HTTP / 1.1 429 < Server: nginx / 1.4 .2 < Date: Mon, 04 Nov 2013 00: 18: 27 GMT < Content - Type: text / html; charset = utf - 8 < Content - Length: 85 < Connection: keep - alive < Status: 429 < Cache - Control: no - cache < X - Zendesk - API - Version: v2 < Retry - After: 93 < X - Zendesk - Origin - Server: ** ** . ** ** . ** * . ** ** * .com < X - Zendesk - User - Id: 338231444 < X - Zendesk - Request - Id: c773675d81590abad33i < * Connection #0 to host SUBDOMAIN.zendesk.com left intact* Closing connection # 0 * SSLv3, TLS alert, Client hello(1): Rate limit for ticket updates exceeded, please wait before updating this ticket again
response = request.get(url) if response.status equals 429: alert('Rate limited. Waiting to retry…') wait(response.retry - after) retry(url)
However, we do provide the following response headers that you can use to confirm the account's current rate limit and monitor the number of requests remaining in the current minute:
X - Rate - Limit: 700 X - Rate - Limit - Remaining: 699
X - Rate - Limit: 700 X - Rate - Limit - Remaining: 699