|
@@ -8,8 +8,7 @@ from django.contrib.auth import logout
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
from django.http import HttpResponse
|
|
from django.http import HttpResponse
|
|
|
from django.http import HttpResponseRedirect
|
|
from django.http import HttpResponseRedirect
|
|
|
-from django.shortcuts import render_to_response
|
|
|
|
|
-from django.template import RequestContext
|
|
|
|
|
|
|
+from django.template.loader import get_template
|
|
|
from django.utils import timezone as datetime
|
|
from django.utils import timezone as datetime
|
|
|
from django.utils.translation import ugettext_lazy
|
|
from django.utils.translation import ugettext_lazy
|
|
|
|
|
|
|
@@ -56,7 +55,8 @@ BEHIND_REVERSE_PROXY_WITH_DIRECT_ACCESS = getattr(settings, 'AXES_BEHIND_REVERSE
|
|
|
REVERSE_PROXY_HEADER = getattr(settings, 'AXES_REVERSE_PROXY_HEADER', 'HTTP_X_FORWARDED_FOR')
|
|
REVERSE_PROXY_HEADER = getattr(settings, 'AXES_REVERSE_PROXY_HEADER', 'HTTP_X_FORWARDED_FOR')
|
|
|
|
|
|
|
|
# lock out user from particular IP based on combination USER+IP
|
|
# lock out user from particular IP based on combination USER+IP
|
|
|
-LOCK_OUT_BY_COMBINATION_USER_AND_IP = getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
|
|
|
|
|
|
|
+def should_lock_out_by_combination_user_and_ip():
|
|
|
|
|
+ return getattr(settings, 'AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP', False)
|
|
|
|
|
|
|
|
COOLOFF_TIME = getattr(settings, 'AXES_COOLOFF_TIME', None)
|
|
COOLOFF_TIME = getattr(settings, 'AXES_COOLOFF_TIME', None)
|
|
|
if (isinstance(COOLOFF_TIME, int) or isinstance(COOLOFF_TIME, float) ):
|
|
if (isinstance(COOLOFF_TIME, int) or isinstance(COOLOFF_TIME, float) ):
|
|
@@ -106,8 +106,8 @@ def get_ip_address_from_request(request):
|
|
|
if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
|
|
if not x_forwarded_for.startswith(PRIVATE_IPS_PREFIX) and is_valid_ip(x_forwarded_for):
|
|
|
ip_address = x_forwarded_for.strip()
|
|
ip_address = x_forwarded_for.strip()
|
|
|
else:
|
|
else:
|
|
|
- ips = [ip.strip() for ip in x_forwarded_for.split(',')]
|
|
|
|
|
- for ip in ips:
|
|
|
|
|
|
|
+ for ip_raw in x_forwarded_for.split(','):
|
|
|
|
|
+ ip = ip_raw.strip()
|
|
|
if ip.startswith(PRIVATE_IPS_PREFIX):
|
|
if ip.startswith(PRIVATE_IPS_PREFIX):
|
|
|
continue
|
|
continue
|
|
|
elif not is_valid_ip(ip):
|
|
elif not is_valid_ip(ip):
|
|
@@ -160,15 +160,11 @@ def query2str(items, max_length=1024):
|
|
|
|
|
|
|
|
If there's a field called "password" it will be excluded from the output.
|
|
If there's a field called "password" it will be excluded from the output.
|
|
|
|
|
|
|
|
- The length of the output is limited to max_length to avoid a DoS attack.
|
|
|
|
|
|
|
+ The length of the output is limited to max_length to avoid a DoS attack via excessively large payloads.
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
- kvs = []
|
|
|
|
|
- for k, v in items:
|
|
|
|
|
- if k != PASSWORD_FORM_FIELD:
|
|
|
|
|
- kvs.append(six.u('%s=%s') % (k, v))
|
|
|
|
|
-
|
|
|
|
|
- return '\n'.join(kvs)[:max_length]
|
|
|
|
|
|
|
+ return '\n'.join(['%s=%s' % (k, v) for k, v in six.iteritems(items)
|
|
|
|
|
+ if k != PASSWORD_FORM_FIELD][:int(max_length/2)])[:max_length]
|
|
|
|
|
|
|
|
|
|
|
|
|
def ip_in_whitelist(ip):
|
|
def ip_in_whitelist(ip):
|
|
@@ -201,7 +197,7 @@ def is_user_lockable(request):
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
if hasattr(user, 'nolockout'):
|
|
if hasattr(user, 'nolockout'):
|
|
|
- # need to revert since we need to return
|
|
|
|
|
|
|
+ # need to invert since we need to return
|
|
|
# false for users that can't be blocked
|
|
# false for users that can't be blocked
|
|
|
return not user.nolockout
|
|
return not user.nolockout
|
|
|
|
|
|
|
@@ -209,7 +205,7 @@ def is_user_lockable(request):
|
|
|
try:
|
|
try:
|
|
|
profile = user.get_profile()
|
|
profile = user.get_profile()
|
|
|
if hasattr(profile, 'nolockout'):
|
|
if hasattr(profile, 'nolockout'):
|
|
|
- # need to revert since we need to return
|
|
|
|
|
|
|
+ # need to invert since we need to return
|
|
|
# false for users that can't be blocked
|
|
# false for users that can't be blocked
|
|
|
return not profile.nolockout
|
|
return not profile.nolockout
|
|
|
|
|
|
|
@@ -238,10 +234,12 @@ def _get_user_attempts(request):
|
|
|
ip_address=ip, username=username, trusted=True
|
|
ip_address=ip, username=username, trusted=True
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
- if not attempts and not LOCK_OUT_BY_COMBINATION_USER_AND_IP:
|
|
|
|
|
|
|
+ if not attempts:
|
|
|
params = {'ip_address': ip, 'trusted': False}
|
|
params = {'ip_address': ip, 'trusted': False}
|
|
|
if USE_USER_AGENT:
|
|
if USE_USER_AGENT:
|
|
|
params['user_agent'] = ua
|
|
params['user_agent'] = ua
|
|
|
|
|
+ if should_lock_out_by_combination_user_and_ip():
|
|
|
|
|
+ params['username'] = username
|
|
|
|
|
|
|
|
attempts = AccessAttempt.objects.filter(**params)
|
|
attempts = AccessAttempt.objects.filter(**params)
|
|
|
|
|
|
|
@@ -342,8 +340,9 @@ def lockout_response(request):
|
|
|
'failure_limit': FAILURE_LIMIT,
|
|
'failure_limit': FAILURE_LIMIT,
|
|
|
'username': request.POST.get(USERNAME_FORM_FIELD, '')
|
|
'username': request.POST.get(USERNAME_FORM_FIELD, '')
|
|
|
}
|
|
}
|
|
|
- return render_to_response(LOCKOUT_TEMPLATE, context,
|
|
|
|
|
- context_instance=RequestContext(request))
|
|
|
|
|
|
|
+ template = get_template(LOCKOUT_TEMPLATE)
|
|
|
|
|
+ content = template.render(context, request)
|
|
|
|
|
+ return HttpResponse(content)
|
|
|
|
|
|
|
|
LOCKOUT_URL = get_lockout_url()
|
|
LOCKOUT_URL = get_lockout_url()
|
|
|
if LOCKOUT_URL:
|
|
if LOCKOUT_URL:
|
|
@@ -367,10 +366,15 @@ def is_already_locked(request):
|
|
|
if ip_in_blacklist(ip):
|
|
if ip_in_blacklist(ip):
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
- attempts = get_user_attempts(request)
|
|
|
|
|
user_lockable = is_user_lockable(request)
|
|
user_lockable = is_user_lockable(request)
|
|
|
|
|
+
|
|
|
|
|
+ if not user_lockable:
|
|
|
|
|
+ return False
|
|
|
|
|
+
|
|
|
|
|
+ attempts = get_user_attempts(request)
|
|
|
|
|
+
|
|
|
for attempt in attempts:
|
|
for attempt in attempts:
|
|
|
- if attempt.failures_since_start >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE and user_lockable:
|
|
|
|
|
|
|
+ if attempt.failures_since_start >= FAILURE_LIMIT and LOCK_OUT_AT_FAILURE:
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
return False
|
|
@@ -395,11 +399,11 @@ def check_request(request, login_unsuccessful):
|
|
|
for attempt in attempts:
|
|
for attempt in attempts:
|
|
|
attempt.get_data = '%s\n---------\n%s' % (
|
|
attempt.get_data = '%s\n---------\n%s' % (
|
|
|
attempt.get_data,
|
|
attempt.get_data,
|
|
|
- query2str(request.GET.items()),
|
|
|
|
|
|
|
+ query2str(request.GET),
|
|
|
)
|
|
)
|
|
|
attempt.post_data = '%s\n---------\n%s' % (
|
|
attempt.post_data = '%s\n---------\n%s' % (
|
|
|
attempt.post_data,
|
|
attempt.post_data,
|
|
|
- query2str(request.POST.items())
|
|
|
|
|
|
|
+ query2str(request.POST)
|
|
|
)
|
|
)
|
|
|
attempt.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')
|
|
attempt.http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')
|
|
|
attempt.path_info = request.META.get('PATH_INFO', '<unknown>')
|
|
attempt.path_info = request.META.get('PATH_INFO', '<unknown>')
|
|
@@ -458,8 +462,8 @@ def create_new_failure_records(request, failures):
|
|
|
'user_agent': ua,
|
|
'user_agent': ua,
|
|
|
'ip_address': ip,
|
|
'ip_address': ip,
|
|
|
'username': username,
|
|
'username': username,
|
|
|
- 'get_data': query2str(request.GET.items()),
|
|
|
|
|
- 'post_data': query2str(request.POST.items()),
|
|
|
|
|
|
|
+ 'get_data': query2str(request.GET),
|
|
|
|
|
+ 'post_data': query2str(request.POST),
|
|
|
'http_accept': request.META.get('HTTP_ACCEPT', '<unknown>'),
|
|
'http_accept': request.META.get('HTTP_ACCEPT', '<unknown>'),
|
|
|
'path_info': request.META.get('PATH_INFO', '<unknown>'),
|
|
'path_info': request.META.get('PATH_INFO', '<unknown>'),
|
|
|
'failures_since_start': failures,
|
|
'failures_since_start': failures,
|
|
@@ -482,8 +486,8 @@ def create_new_trusted_record(request):
|
|
|
user_agent=ua,
|
|
user_agent=ua,
|
|
|
ip_address=ip,
|
|
ip_address=ip,
|
|
|
username=username,
|
|
username=username,
|
|
|
- get_data=query2str(request.GET.items()),
|
|
|
|
|
- post_data=query2str(request.POST.items()),
|
|
|
|
|
|
|
+ get_data=query2str(request.GET),
|
|
|
|
|
+ post_data=query2str(request.POST),
|
|
|
http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
|
|
http_accept=request.META.get('HTTP_ACCEPT', '<unknown>'),
|
|
|
path_info=request.META.get('PATH_INFO', '<unknown>'),
|
|
path_info=request.META.get('PATH_INFO', '<unknown>'),
|
|
|
failures_since_start=0,
|
|
failures_since_start=0,
|