mirror of
https://github.com/jazzband/django-axes.git
synced 2026-05-15 19:13:13 +00:00
169 lines
5.9 KiB
Python
169 lines
5.9 KiB
Python
import logging
|
|
|
|
from django.contrib.auth.signals import user_logged_in
|
|
from django.contrib.auth.signals import user_logged_out
|
|
from django.contrib.auth.signals import user_login_failed
|
|
from django.core.cache import cache
|
|
from django.db.models.signals import post_save, post_delete
|
|
from django.dispatch import receiver
|
|
from django.dispatch import Signal
|
|
from django.utils import timezone
|
|
|
|
from axes.conf import settings
|
|
from axes.attempts import get_cache_key
|
|
from axes.attempts import get_cache_timeout
|
|
from axes.attempts import get_user_attempts
|
|
from axes.attempts import is_user_lockable
|
|
from axes.attempts import ip_in_whitelist
|
|
from axes.models import AccessLog, AccessAttempt
|
|
from axes.utils import get_client_str
|
|
from axes.utils import get_ip
|
|
from axes.utils import query2str
|
|
|
|
|
|
log = logging.getLogger(settings.AXES_LOGGER)
|
|
|
|
|
|
user_locked_out = Signal(providing_args=['request', 'username', 'ip_address'])
|
|
|
|
|
|
@receiver(user_login_failed)
|
|
def log_user_login_failed(sender, credentials, request, **kwargs):
|
|
""" Create an AccessAttempt record if the login wasn't successful
|
|
"""
|
|
ip_address = get_ip(request)
|
|
username = credentials['username']
|
|
user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
|
|
path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
|
|
http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]
|
|
|
|
if settings.AXES_NEVER_LOCKOUT_WHITELIST and ip_in_whitelist(ip_address):
|
|
return
|
|
|
|
failures = 1
|
|
attempts = get_user_attempts(request)
|
|
cache_hash_key = get_cache_key(request)
|
|
cache_timeout = get_cache_timeout()
|
|
|
|
failures_cached = cache.get(cache_hash_key)
|
|
if failures_cached is not None:
|
|
failures = failures_cached
|
|
else:
|
|
for attempt in attempts:
|
|
failures = max(failures, attempt.failures_since_start)
|
|
|
|
# add a failed attempt for this user
|
|
failures += 1
|
|
cache.set(cache_hash_key, failures, cache_timeout)
|
|
|
|
# has already attempted, update the info
|
|
if len(attempts):
|
|
for attempt in attempts:
|
|
attempt.get_data = '%s\n---------\n%s' % (
|
|
attempt.get_data,
|
|
query2str(request.GET),
|
|
)
|
|
attempt.post_data = '%s\n---------\n%s' % (
|
|
attempt.post_data,
|
|
query2str(request.POST)
|
|
)
|
|
attempt.http_accept = http_accept
|
|
attempt.path_info = path_info
|
|
attempt.failures_since_start = failures
|
|
attempt.attempt_time = timezone.now()
|
|
attempt.save()
|
|
|
|
fail_msg = 'AXES: Repeated login failure by {0}.'.format(
|
|
get_client_str(username, ip_address, user_agent, path_info)
|
|
)
|
|
count_msg = 'Count = {0} of {1}'.format(
|
|
failures, settings.AXES_FAILURE_LIMIT
|
|
)
|
|
log.info('{0} {1}'.format(fail_msg, count_msg))
|
|
else:
|
|
# Record failed attempt. Whether or not the IP address or user agent is
|
|
# used in counting failures is handled elsewhere, so we just record
|
|
# everything here.
|
|
AccessAttempt.objects.create(
|
|
user_agent=user_agent,
|
|
ip_address=ip_address,
|
|
username=username,
|
|
get_data=query2str(request.GET),
|
|
post_data=query2str(request.POST),
|
|
http_accept=http_accept,
|
|
path_info=path_info,
|
|
failures_since_start=failures,
|
|
)
|
|
|
|
log.info(
|
|
'AXES: New login failure by {0}. Creating access record.'.format(
|
|
get_client_str(username, ip_address, user_agent, path_info)
|
|
)
|
|
)
|
|
|
|
# no matter what, we want to lock them out if they're past the number of
|
|
# attempts allowed, unless the user is set to notlockable
|
|
if (
|
|
failures >= settings.AXES_FAILURE_LIMIT and
|
|
settings.AXES_LOCK_OUT_AT_FAILURE and
|
|
is_user_lockable(request)
|
|
):
|
|
log.warn('AXES: locked out {0} after repeated login attempts.'.format(
|
|
get_client_str(username, ip_address, user_agent, path_info)
|
|
))
|
|
|
|
# send signal when someone is locked out.
|
|
user_locked_out.send(
|
|
'axes', request=request, username=username, ip_address=ip_address
|
|
)
|
|
|
|
|
|
@receiver(user_logged_in)
|
|
def log_user_logged_in(sender, request, user, **kwargs):
|
|
""" When a user logs in, update the access log
|
|
"""
|
|
username = user.get_username()
|
|
ip_address = get_ip(request)
|
|
user_agent = request.META.get('HTTP_USER_AGENT', '<unknown>')[:255]
|
|
path_info = request.META.get('PATH_INFO', '<unknown>')[:255]
|
|
http_accept = request.META.get('HTTP_ACCEPT', '<unknown>')[:1025]
|
|
log.info('AXES: Successful login by {0}.'.format(
|
|
get_client_str(username, ip_address, user_agent, path_info)
|
|
))
|
|
|
|
if not settings.AXES_DISABLE_SUCCESS_ACCESS_LOG:
|
|
AccessLog.objects.create(
|
|
user_agent=user_agent,
|
|
ip_address=ip_address,
|
|
username=username,
|
|
http_accept=http_accept,
|
|
path_info=path_info,
|
|
trusted=True,
|
|
)
|
|
|
|
|
|
@receiver(user_logged_out)
|
|
def log_user_logged_out(sender, request, user, **kwargs):
|
|
""" When a user logs out, update the access log
|
|
"""
|
|
log.info('AXES: Successful logout by {0}.'.format(user))
|
|
|
|
if user and not settings.AXES_DISABLE_ACCESS_LOG:
|
|
AccessLog.objects.filter(
|
|
username=user.get_username(),
|
|
logout_time__isnull=True,
|
|
).update(logout_time=timezone.now())
|
|
|
|
|
|
@receiver(post_save, sender=AccessAttempt)
|
|
def update_cache_after_save(instance, **kwargs):
|
|
cache_hash_key = get_cache_key(instance)
|
|
if not cache.get(cache_hash_key):
|
|
cache_timeout = get_cache_timeout()
|
|
cache.set(cache_hash_key, instance.failures_since_start, cache_timeout)
|
|
|
|
|
|
@receiver(post_delete, sender=AccessAttempt)
|
|
def delete_cache_after_delete(instance, **kwargs):
|
|
cache_hash_key = get_cache_key(instance)
|
|
cache.delete(cache_hash_key)
|