# Path: axes/conf.py

# 
# Path: axes/helpers.py
# def get_client_username(request, credentials: dict = None) -> str:
#     """
#     Resolve client username from the given request or credentials if supplied.
# 
#     The order of preference for fetching the username is as follows:
# 
#     1. If configured, use ``AXES_USERNAME_CALLABLE``, and supply ``request, credentials`` as arguments
#     2. If given, use ``credentials`` and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
#     3. Use request.POST and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
# 
#     :param request: incoming Django ``HttpRequest`` or similar object from authentication backend or other source
#     :param credentials: incoming credentials ``dict`` or similar object from authentication backend or other source
#     """
# 
#     if settings.AXES_USERNAME_CALLABLE:
#         log.debug("Using settings.AXES_USERNAME_CALLABLE to get username")
# 
#         if callable(settings.AXES_USERNAME_CALLABLE):
#             return settings.AXES_USERNAME_CALLABLE(  # pylint: disable=not-callable
#                 request, credentials
#             )
#         if isinstance(settings.AXES_USERNAME_CALLABLE, str):
#             return import_string(settings.AXES_USERNAME_CALLABLE)(request, credentials)
#         raise TypeError(
#             "settings.AXES_USERNAME_CALLABLE needs to be a string, callable, or None."
#         )
# 
#     if credentials:
#         log.debug(
#             "Using parameter credentials to get username with key settings.AXES_USERNAME_FORM_FIELD"
#         )
#         return credentials.get(settings.AXES_USERNAME_FORM_FIELD, None)
# 
#     log.debug(
#         "Using parameter request.POST to get username with key settings.AXES_USERNAME_FORM_FIELD"
#     )
# 
#     request_data = getattr(request, "data", request.POST)
#     return request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
# 
# def get_client_parameters(username: str, ip_address: str, user_agent: str) -> list:
#     """
#     Get query parameters for filtering AccessAttempt queryset.
# 
#     This method returns a dict that guarantees iteration order for keys and values,
#     and can so be used in e.g. the generation of hash keys or other deterministic functions.
# 
#     Returns list of dict, every item of list are separate parameters
#     """
# 
#     if settings.AXES_ONLY_USER_FAILURES:
#         # 1. Only individual usernames can be tracked with parametrization
#         filter_query = [{"username": username}]
#     else:
#         if settings.AXES_LOCK_OUT_BY_USER_OR_IP:
#             # One of `username` or `IP address` is used
#             filter_query = [{"username": username}, {"ip_address": ip_address}]
#         elif settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
#             # 2. A combination of username and IP address can be used as well
#             filter_query = [{"username": username, "ip_address": ip_address}]
#         else:
#             # 3. Default case is to track the IP address only, which is the most secure option
#             filter_query = [{"ip_address": ip_address}]
# 
#         if settings.AXES_USE_USER_AGENT:
#             # 4. The HTTP User-Agent can be used to track e.g. one browser
#             filter_query.append({"user_agent": user_agent})
# 
#     return filter_query
# 
# def get_cool_off() -> Optional[timedelta]:
#     """
#     Return the login cool off time interpreted from settings.AXES_COOLOFF_TIME.
# 
#     The return value is either None or timedelta.
# 
#     Notice that the settings.AXES_COOLOFF_TIME is either None, timedelta, or integer of hours,
#     and this function offers a unified _timedelta or None_ representation of that configuration
#     for use with the Axes internal implementations.
# 
#     :exception TypeError: if settings.AXES_COOLOFF_TIME is of wrong type.
#     """
# 
#     cool_off = settings.AXES_COOLOFF_TIME
# 
#     if isinstance(cool_off, int):
#         return timedelta(hours=cool_off)
#     if isinstance(cool_off, str):
#         return import_string(cool_off)()
#     if callable(cool_off):
#         return cool_off()  # pylint: disable=not-callable
# 
#     return cool_off
# 
# Path: axes/models.py
# class AccessAttempt(AccessBase):
#     get_data = models.TextField(_("GET Data"))
# 
#     post_data = models.TextField(_("POST Data"))
# 
#     failures_since_start = models.PositiveIntegerField(_("Failed Logins"))
# 
#     def __str__(self):
#         return f"Attempted Access: {self.attempt_time}"
# 
#     class Meta:
#         verbose_name = _("access attempt")
#         verbose_name_plural = _("access attempts")
#         unique_together = [["username", "ip_address", "user_agent"]]


from logging import getLogger
from typing import List
from django.db.models import QuerySet
from django.utils.timezone import datetime, now
from axes.conf import settings
from axes.helpers import get_client_username, get_client_parameters, get_cool_off
from axes.models import AccessAttempt
def get_user_attempts(request, credentials: dict = None) -> List[QuerySet]:
    """
    Get list of querysets with valid user attempts that match the given request and credentials.
    """

    attempts_list = filter_user_attempts(request, credentials)

    if settings.AXES_COOLOFF_TIME is None:
        log.debug(
            "AXES: Getting all access attempts from database because no AXES_COOLOFF_TIME is configured"
        )
        return attempts_list

    threshold = get_cool_off_threshold(request.axes_attempt_time)
    log.debug("AXES: Getting access attempts that are newer than %s", threshold)
    return [attempts.filter(attempt_time__gte=threshold) for attempts in attempts_list]


def clean_expired_user_attempts(attempt_time: datetime = None) -> int:
    """
    Clean expired user attempts from the database.
    """

    if settings.AXES_COOLOFF_TIME is None:
        log.debug(
            "AXES: Skipping clean for expired access attempts because no AXES_COOLOFF_TIME is configured"
        )
        return 0

    threshold = get_cool_off_threshold(attempt_time)

# Path: axes/conf.py

# 
# Path: axes/helpers.py
# def get_client_username(request, credentials: dict = None) -> str:
#     """
#     Resolve client username from the given request or credentials if supplied.
# 
#     The order of preference for fetching the username is as follows:
# 
#     1. If configured, use ``AXES_USERNAME_CALLABLE``, and supply ``request, credentials`` as arguments
#     2. If given, use ``credentials`` and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
#     3. Use request.POST and fetch username from ``AXES_USERNAME_FORM_FIELD`` (defaults to ``username``)
# 
#     :param request: incoming Django ``HttpRequest`` or similar object from authentication backend or other source
#     :param credentials: incoming credentials ``dict`` or similar object from authentication backend or other source
#     """
# 
#     if settings.AXES_USERNAME_CALLABLE:
#         log.debug("Using settings.AXES_USERNAME_CALLABLE to get username")
# 
#         if callable(settings.AXES_USERNAME_CALLABLE):
#             return settings.AXES_USERNAME_CALLABLE(  # pylint: disable=not-callable
#                 request, credentials
#             )
#         if isinstance(settings.AXES_USERNAME_CALLABLE, str):
#             return import_string(settings.AXES_USERNAME_CALLABLE)(request, credentials)
#         raise TypeError(
#             "settings.AXES_USERNAME_CALLABLE needs to be a string, callable, or None."
#         )
# 
#     if credentials:
#         log.debug(
#             "Using parameter credentials to get username with key settings.AXES_USERNAME_FORM_FIELD"
#         )
#         return credentials.get(settings.AXES_USERNAME_FORM_FIELD, None)
# 
#     log.debug(
#         "Using parameter request.POST to get username with key settings.AXES_USERNAME_FORM_FIELD"
#     )
# 
#     request_data = getattr(request, "data", request.POST)
#     return request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
# 
# def get_client_parameters(username: str, ip_address: str, user_agent: str) -> list:
#     """
#     Get query parameters for filtering AccessAttempt queryset.
# 
#     This method returns a dict that guarantees iteration order for keys and values,
#     and can so be used in e.g. the generation of hash keys or other deterministic functions.
# 
#     Returns list of dict, every item of list are separate parameters
#     """
# 
#     if settings.AXES_ONLY_USER_FAILURES:
#         # 1. Only individual usernames can be tracked with parametrization
#         filter_query = [{"username": username}]
#     else:
#         if settings.AXES_LOCK_OUT_BY_USER_OR_IP:
#             # One of `username` or `IP address` is used
#             filter_query = [{"username": username}, {"ip_address": ip_address}]
#         elif settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
#             # 2. A combination of username and IP address can be used as well
#             filter_query = [{"username": username, "ip_address": ip_address}]
#         else:
#             # 3. Default case is to track the IP address only, which is the most secure option
#             filter_query = [{"ip_address": ip_address}]
# 
#         if settings.AXES_USE_USER_AGENT:
#             # 4. The HTTP User-Agent can be used to track e.g. one browser
#             filter_query.append({"user_agent": user_agent})
# 
#     return filter_query
# 
# def get_cool_off() -> Optional[timedelta]:
#     """
#     Return the login cool off time interpreted from settings.AXES_COOLOFF_TIME.
# 
#     The return value is either None or timedelta.
# 
#     Notice that the settings.AXES_COOLOFF_TIME is either None, timedelta, or integer of hours,
#     and this function offers a unified _timedelta or None_ representation of that configuration
#     for use with the Axes internal implementations.
# 
#     :exception TypeError: if settings.AXES_COOLOFF_TIME is of wrong type.
#     """
# 
#     cool_off = settings.AXES_COOLOFF_TIME
# 
#     if isinstance(cool_off, int):
#         return timedelta(hours=cool_off)
#     if isinstance(cool_off, str):
#         return import_string(cool_off)()
#     if callable(cool_off):
#         return cool_off()  # pylint: disable=not-callable
# 
#     return cool_off
# 
# Path: axes/models.py
# class AccessAttempt(AccessBase):
#     get_data = models.TextField(_("GET Data"))
# 
#     post_data = models.TextField(_("POST Data"))
# 
#     failures_since_start = models.PositiveIntegerField(_("Failed Logins"))
# 
#     def __str__(self):
#         return f"Attempted Access: {self.attempt_time}"
# 
#     class Meta:
#         verbose_name = _("access attempt")
#         verbose_name_plural = _("access attempts")
#         unique_together = [["username", "ip_address", "user_agent"]]


# Path: axes/attempts.py
from logging import getLogger
from typing import List
from django.db.models import QuerySet
from django.utils.timezone import datetime, now
from axes.conf import settings
from axes.helpers import get_client_username, get_client_parameters, get_cool_off
from axes.models import AccessAttempt

def get_user_attempts(request, credentials: dict = None) -> List[QuerySet]:
    """
    Get list of querysets with valid user attempts that match the given request and credentials.
    """

    attempts_list = filter_user_attempts(request, credentials)

    if settings.AXES_COOLOFF_TIME is None:
        log.debug(
            "AXES: Getting all access attempts from database because no AXES_COOLOFF_TIME is configured"
        )
        return attempts_list

    threshold = get_cool_off_threshold(request.axes_attempt_time)
    log.debug("AXES: Getting access attempts that are newer than %s", threshold)
    return [attempts.filter(attempt_time__gte=threshold) for attempts in attempts_list]


def clean_expired_user_attempts(attempt_time: datetime = None) -> int:
    """
    Clean expired user attempts from the database.
    """

    if settings.AXES_COOLOFF_TIME is None:
        log.debug(
            "AXES: Skipping clean for expired access attempts because no AXES_COOLOFF_TIME is configured"
        )
        return 0

    threshold = get_cool_off_threshold(attempt_time)

    count, _ = AccessAttempt.objects.filter(attempt_time__lt=threshold).delete()