Compare commits

..

515 commits

Author SHA1 Message Date
Enrico Tröger
fdd7b22cd3 Clarify and/or conditions in AXES_LOCKOUT_PARAMETERS examples
Some checks are pending
Test / build (Python 3.10, Django 4.2) (push) Waiting to run
Test / build (Python 3.11, Django 4.2) (push) Waiting to run
Test / build (Python 3.12, Django 4.2) (push) Waiting to run
Test / build (Python 3.14, Django 4.2) (push) Waiting to run
Test / build (Python 3.10, Django 5.2) (push) Waiting to run
Test / build (Python 3.11, Django 5.2) (push) Waiting to run
Test / build (Python 3.12, Django 5.2) (push) Waiting to run
Test / build (Python 3.13, Django 5.2) (push) Waiting to run
Test / build (Python 3.14, Django 5.2) (push) Waiting to run
Test / build (Python 3.12, Django 6.0) (push) Waiting to run
Test / build (Python 3.13, Django 6.0) (push) Waiting to run
Test / build (Python 3.14, Django 6.0) (push) Waiting to run
Test / build (Python 3.14, Django main) (push) Waiting to run
Test / build (Python 3.14, Django qa) (push) Waiting to run
2026-03-16 18:55:20 +02:00
dependabot[bot]
a5d14cd630 chore(deps): bump black from 26.1.0 to 26.3.1
Bumps [black](https://github.com/psf/black) from 26.1.0 to 26.3.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/26.1.0...26.3.1)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 18:54:27 +02:00
dependabot[bot]
2a31c0133f chore(deps): bump tox from 4.34.1 to 4.49.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.34.1 to 4.49.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.34.1...4.49.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-version: 4.49.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 18:54:17 +02:00
dependabot[bot]
4624eed684 chore(deps): bump pytest-django from 4.11.1 to 4.12.0
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.11.1 to 4.12.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.11.1...v4.12.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-version: 4.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 18:54:13 +02:00
Aleksi Häkli
e27ce891ea
Version 8.3.1
Some checks failed
Test / build (Python 3.10, Django 4.2) (push) Has been cancelled
Test / build (Python 3.11, Django 4.2) (push) Has been cancelled
Test / build (Python 3.12, Django 4.2) (push) Has been cancelled
Test / build (Python 3.14, Django 4.2) (push) Has been cancelled
Test / build (Python 3.10, Django 5.2) (push) Has been cancelled
Test / build (Python 3.11, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 5.2) (push) Has been cancelled
Test / build (Python 3.13, Django 5.2) (push) Has been cancelled
Test / build (Python 3.14, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 6.0) (push) Has been cancelled
Test / build (Python 3.13, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django main) (push) Has been cancelled
Test / build (Python 3.14, Django qa) (push) Has been cancelled
2026-02-11 22:15:47 +02:00
dependabot[bot]
c3dcd1ba51 chore(deps): bump coverage from 7.13.3 to 7.13.4
Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.3 to 7.13.4.
- [Release notes](https://github.com/coveragepy/coveragepy/releases)
- [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst)
- [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.3...7.13.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.13.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-11 22:14:42 +02:00
Aleksi Häkli
41ebdc3063 Try to run all tox QA commands even if some fail 2026-02-11 22:14:31 +02:00
Aleksi Häkli
31c69dbea5 Simplify black formatting rules 2026-02-11 22:14:31 +02:00
Aleksi Häkli
bdd0c9546a Fix prospector errors 2026-02-11 22:14:31 +02:00
Aleksi Häkli
4b77eb69ee Run black autoformatting 2026-02-11 22:14:31 +02:00
Aleksi Häkli
5acae054b4 Update Black formatting rules 2026-02-11 22:14:31 +02:00
Aleksi Häkli
d59a289407 Suppress mypy type errors
Update Mypy Python version to 3.14
2026-02-11 22:14:31 +02:00
Aleksi Häkli
23ee2fca44 Update version support matrix to run tox QA tests properly on GitHub 2026-02-11 22:14:31 +02:00
Aleksi Häkli
4ea615811b Implement custom lazy object to avoid JSON errors with Celery
Fixes jazzband/django-axes#1391
2026-02-11 22:14:31 +02:00
Aleksi Häkli
b4fb3088b4
Version 8.3.0
Some checks failed
Test / build (Python 3.10, Django 4.2) (push) Has been cancelled
Test / build (Python 3.11, Django 4.2) (push) Has been cancelled
Test / build (Python 3.12, Django 4.2) (push) Has been cancelled
Test / build (Python 3.14, Django 4.2) (push) Has been cancelled
Test / build (Python 3.10, Django 5.2) (push) Has been cancelled
Test / build (Python 3.11, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 5.2) (push) Has been cancelled
Test / build (Python 3.13, Django 5.2) (push) Has been cancelled
Test / build (Python 3.14, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 6.0) (push) Has been cancelled
Test / build (Python 3.13, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django main) (push) Has been cancelled
Test / build (Python 3.14, Django qa) (push) Has been cancelled
2026-02-09 10:37:38 +02:00
Hugo van Kemenade
6c8feada83 Replace removed pkg_resources with stdlib 2026-02-09 10:35:54 +02:00
Aleksi Häkli
b441ccd5fc
Version 8.2.0
Some checks failed
Test / build (Python 3.10, Django 4.2) (push) Has been cancelled
Test / build (Python 3.11, Django 4.2) (push) Has been cancelled
Test / build (Python 3.12, Django 4.2) (push) Has been cancelled
Test / build (Python 3.14, Django 4.2) (push) Has been cancelled
Test / build (Python 3.10, Django 5.2) (push) Has been cancelled
Test / build (Python 3.11, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 5.2) (push) Has been cancelled
Test / build (Python 3.13, Django 5.2) (push) Has been cancelled
Test / build (Python 3.14, Django 5.2) (push) Has been cancelled
Test / build (Python 3.12, Django 6.0) (push) Has been cancelled
Test / build (Python 3.13, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django 6.0) (push) Has been cancelled
Test / build (Python 3.14, Django main) (push) Has been cancelled
Test / build (Python 3.14, Django qa) (push) Has been cancelled
2026-02-06 20:50:14 +02:00
dependabot[bot]
1d9964be16 chore(deps): bump tox from 4.32.0 to 4.34.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.32.0 to 4.34.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.32.0...4.34.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-version: 4.34.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 20:37:05 +02:00
dependabot[bot]
60e3cceb1d chore(deps): bump black from 25.11.0 to 26.1.0
Bumps [black](https://github.com/psf/black) from 25.11.0 to 26.1.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.11.0...26.1.0)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 26.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 20:36:57 +02:00
shayan taki
8f5e9965d8 Add unit tests for security check W006 2026-02-06 20:31:41 +02:00
dependabot[bot]
cf0be90f11 chore(deps): bump sphinx-rtd-theme from 3.0.2 to 3.1.0
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 3.0.2 to 3.1.0.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/3.0.2...3.1.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-version: 3.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 20:23:07 +02:00
dependabot[bot]
d033b70235 chore(deps): bump prospector from 1.17.3 to 1.18.0
Bumps [prospector](https://github.com/prospector-dev/prospector) from 1.17.3 to 1.18.0.
- [Release notes](https://github.com/prospector-dev/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/prospector-dev/prospector/compare/v1.17.3...v1.18.0)

---
updated-dependencies:
- dependency-name: prospector
  dependency-version: 1.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 20:22:52 +02:00
dependabot[bot]
b14e861631 chore(deps): bump coverage from 7.13.0 to 7.13.3
Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.13.0 to 7.13.3.
- [Release notes](https://github.com/coveragepy/coveragepy/releases)
- [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst)
- [Commits](https://github.com/coveragepy/coveragepy/compare/7.13.0...7.13.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.13.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 20:22:46 +02:00
rodrigo.nogueira
6703b66f17 Fix circular import with custom user models
Fixes #1280

- Use SimpleLazyObject to defer get_user_model() evaluation
- Prevents circular import when custom user models import from axes
- Add test coverage for lazy evaluation in test_conf.py
2026-02-06 20:19:42 +02:00
rodrigo.nogueira
95a8043341 Fix AttributeError when optional settings are undefined
Fixes #1328
- Add None as default value in axes_conf_check
- Add test coverage for missing settings scenario
2026-02-06 20:19:42 +02:00
Aleksi Häkli
f2af7c993b chore: remove deprecated requirements-*.txt files 2025-12-19 22:07:09 +02:00
Aleksi Häkli
8869a9e594 chore: update dependencies 2025-12-19 22:02:28 +02:00
Aleksi Häkli
0735c71432 chore: use single requirements file
Multiple files seem to break some IDEs and package management solutions
2025-12-19 21:54:43 +02:00
Aleksi Häkli
332a5f57d0 Version 8.1.0 2025-12-19 21:41:15 +02:00
Aleksi Häkli
a30b68aec9 chore: update mypy to 1.19.1 2025-12-19 21:28:28 +02:00
dependabot[bot]
29005e2f6f chore(deps): bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:20:39 +02:00
dependabot[bot]
dd172ec1a5 chore(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:20:19 +02:00
dependabot[bot]
0d5795cdf2 chore(deps): bump black from 25.9.0 to 25.11.0
Bumps [black](https://github.com/psf/black) from 25.9.0 to 25.11.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.9.0...25.11.0)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 25.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:20:12 +02:00
dependabot[bot]
2fce8fafdf chore(deps): bump pytest-subtests from 0.14.2 to 0.15.0
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.14.2 to 0.15.0.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/v0.14.2...v0.15.0)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:20:06 +02:00
dependabot[bot]
53dfc9a821 chore(deps): bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:19:59 +02:00
dependabot[bot]
9ba27a0755 chore(deps): bump coverage from 7.9.2 to 7.10.7
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.9.2 to 7.10.7.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.9.2...7.10.7)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.10.7
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 21:19:52 +02:00
Aleksi Häkli
6866a53728 chore: add Python 3.14 and Django 6.0 support 2025-12-19 21:19:15 +02:00
Mounir Messelmeni
955f39da73 Update docs 2025-12-19 21:19:03 +02:00
Mounir Messelmeni
04fd39fa57 Enhance get_lockout_response to support original_response parameter 2025-12-19 21:19:03 +02:00
Ram
69c97d5c7b docs: Add missing AXES_IPWARE_PROXY_ORDER setting documentation 2025-12-19 21:14:16 +02:00
shayan taki
3f6e773f7d Add security check (W006) for missing ip_address in lockout params 2025-12-19 21:13:43 +02:00
Ameer Taweel
88827c381e docs: Clarify ordering of AxesMiddleware
Closes #1015
2025-10-08 14:55:21 +03:00
dependabot[bot]
e8c3bf7be7 chore(deps): bump black from 25.1.0 to 25.9.0
Bumps [black](https://github.com/psf/black) from 25.1.0 to 25.9.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/25.1.0...25.9.0)

---
updated-dependencies:
- dependency-name: black
  dependency-version: 25.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:54:43 +03:00
dependabot[bot]
9962313199 chore(deps): bump pytest from 8.4.0 to 8.4.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.0 to 8.4.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.4.0...8.4.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 8.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:54:30 +03:00
dependabot[bot]
cf3d3eda2c chore(deps): bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:54:23 +03:00
dependabot[bot]
2a02585d23 chore(deps): bump prospector from 1.17.2 to 1.17.3
Bumps [prospector](https://github.com/prospector-dev/prospector) from 1.17.2 to 1.17.3.
- [Release notes](https://github.com/prospector-dev/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/prospector-dev/prospector/compare/v1.17.2...v1.17.3)

---
updated-dependencies:
- dependency-name: prospector
  dependency-version: 1.17.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:53:33 +03:00
dependabot[bot]
13f293b650 chore(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:53:13 +03:00
dependabot[bot]
592452e446 chore(deps): bump tox from 4.27.0 to 4.30.3
Bumps [tox](https://github.com/tox-dev/tox) from 4.27.0 to 4.30.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.27.0...4.30.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-version: 4.30.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 14:52:59 +03:00
kuldeepkhatke
2a8c42c3eb Added cleanup_expired_attempts action 2025-07-08 21:09:10 +03:00
kuldeepkhatke
29fd4bd4fe Added IsLockedOutFilter to AccessAttemptAdmin 2025-07-06 23:06:41 +03:00
kuldeepkhatke
af65488dc6 Added status column to list display 2025-07-06 23:06:41 +03:00
kuldeepkhatke
e4e0299252 AccessAttemptAdmin.list_display datatype change tuple->list for customization 2025-07-06 23:06:41 +03:00
dependabot[bot]
75c29bd6f8 chore(deps): bump coverage from 7.8.2 to 7.9.2
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.8.2 to 7.9.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.8.2...7.9.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.9.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 16:24:01 +03:00
dependabot[bot]
95f321e7c7 chore(deps): bump tox from 4.26.0 to 4.27.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.26.0 to 4.27.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.26.0...4.27.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-version: 4.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 16:23:53 +03:00
dependabot[bot]
bebbbe924e chore(deps): bump pytest-subtests from 0.14.1 to 0.14.2
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.14.1 to 0.14.2.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/v0.14.1...v0.14.2)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-version: 0.14.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 16:23:44 +03:00
dependabot[bot]
34a350568e chore(deps): bump pytest-cov from 6.1.1 to 6.2.1
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.1.1 to 6.2.1.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.1.1...v6.2.1)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-version: 6.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 16:23:36 +03:00
dependabot[bot]
8340a7a82f chore(deps): bump prospector from 1.17.1 to 1.17.2
Bumps [prospector](https://github.com/prospector-dev/prospector) from 1.17.1 to 1.17.2.
- [Release notes](https://github.com/prospector-dev/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/prospector-dev/prospector/compare/v1.17.1...v1.17.2)

---
updated-dependencies:
- dependency-name: prospector
  dependency-version: 1.17.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-05 16:23:20 +03:00
kuldeepkhatke
392dfa0e44 Reverted , remove change from AxesDatabaseHandler.user_login_failed 2025-07-05 16:19:19 +03:00
kuldeepkhatke
baace5c27b Added separate UT for complete, partial & no delete senarios 2025-07-05 16:19:19 +03:00
kuldeepkhatke
ba7b72f9d9 Updated expires_at for null, blank False, lte query update, admin expiration logic simplify 2025-07-05 16:19:19 +03:00
kuldeepkhatke
01ccf5b213 Updated get_individual_attempt_expiry() func placement & renamed to get_attempt_expiration() 2025-07-05 16:19:19 +03:00
kuldeepkhatke
d8e6c939fe Modified expiration create queryset logic 2025-07-05 16:19:19 +03:00
kuldeepkhatke
94a66c7346 Added actual queryset call UT 2025-07-05 16:19:19 +03:00
kuldeepkhatke
f5951e966c Modified access_attempt as pk 2025-07-05 16:19:19 +03:00
kuldeepkhatke
f583e93718 Modified verbiage changes & removed comment 2025-07-05 16:19:19 +03:00
kuldeepkhatke
74c24c0e78 Added unittest for AXES_USE_ATTEMPT_EXPIRATION flag 2025-07-05 16:19:19 +03:00
kuldeepkhatke
df8fb35e18 Shifted epired_at filed to new model 2025-07-05 16:19:19 +03:00
kuldeepkhatke
a1e9eff875 Renamed AXES_INDIVIDUAL_ATTEMPT_EXPIRY flag to AXES_USE_ATTEMPT_EXPIRATION 2025-07-05 16:19:19 +03:00
kuldeepkhatke
0fd9ccd1d4 Added individual attempt expiry feature 2025-07-05 16:19:19 +03:00
dependabot[bot]
864dfc2d9a
Merge pull request #1315 from jazzband/dependabot/pip/pytest-8.4.0 2025-06-06 17:19:25 +00:00
dependabot[bot]
d1fad02076
Merge pull request #1314 from jazzband/dependabot/pip/mypy-1.16.0 2025-06-06 17:18:44 +00:00
dependabot[bot]
d79c7de4e5
chore(deps): bump pytest from 8.3.5 to 8.4.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.5 to 8.4.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...8.4.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 17:14:34 +00:00
dependabot[bot]
c9f092a3be
chore(deps): bump mypy from 1.15.0 to 1.16.0
Bumps [mypy](https://github.com/python/mypy) from 1.15.0 to 1.16.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-version: 1.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 17:14:32 +00:00
dependabot[bot]
9becd0061e chore(deps): bump coverage from 7.8.0 to 7.8.2
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.8.0 to 7.8.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.8.0...7.8.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-version: 7.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 20:13:06 +03:00
dependabot[bot]
7e495fb5fd chore(deps): bump prospector from 1.16.1 to 1.17.1
Bumps [prospector](https://github.com/prospector-dev/prospector) from 1.16.1 to 1.17.1.
- [Release notes](https://github.com/prospector-dev/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/prospector-dev/prospector/compare/v1.16.1...v1.17.1)

---
updated-dependencies:
- dependency-name: prospector
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 20:12:50 +03:00
dependabot[bot]
6cb8dc7a46 chore(deps): bump tox from 4.25.0 to 4.26.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.25.0 to 4.26.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.25.0...4.26.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-version: 4.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-06 20:12:41 +03:00
AmirAli Bahramjerdi
a340dec892
Merge pull request #1308 from AmirAli-BahramJerdi/add-farsi-locale
Add Persian (fa) translation for django-axes
2025-05-16 13:28:54 -07:00
AmirAli-BahramJerdi
eea9939a45 Add Persian translation for django-axes 2025-05-16 23:49:58 +03:30
Aleksi Häkli
31038278bd
Add version 8 migration notes. 2025-05-10 14:01:18 +03:00
Aleksi Häkli
a58344c3ef
Remove Django 5.0 from supported versions 2025-05-10 13:56:07 +03:00
Aleksi Häkli
9bc11398f4
Remove Django 5.0 from supported versions 2025-05-10 13:55:20 +03:00
Aleksi Häkli
dfa39d07c0
Remove Django 5.0 from supported versions 2025-05-10 13:54:14 +03:00
Aleksi Häkli
6d2c7b1431
Version 8.0.0 2025-05-10 13:53:23 +03:00
nefrob
bd3b56237d refactor: move db accessing attempt fns to handler methods 2025-05-10 13:46:09 +03:00
nefrob
8356498a44 chore: clean attempt logic as db handler method 2025-04-29 10:10:25 +03:00
Aleksi Häkli
933756090a
Version 7.1.0 2025-04-23 18:52:54 +03:00
dependabot[bot]
3ff5ada46d chore(deps): bump pytest from 8.3.4 to 8.3.5
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.4 to 8.3.5.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.4...8.3.5)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 17:03:06 +03:00
dependabot[bot]
4115d59d14 chore(deps): bump pytest-django from 4.10.0 to 4.11.1
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.10.0 to 4.11.1.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.10.0...v4.11.1)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-version: 4.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 17:02:55 +03:00
Aleksi Häkli
5e7fbca52c fix: resolve credentials for clean_expired_user_attempts 2025-04-23 17:02:37 +03:00
dependabot[bot]
599fbc0da0 chore(deps): bump tox from 4.24.1 to 4.25.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.24.1 to 4.25.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.24.1...4.25.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 11:59:22 +03:00
dependabot[bot]
b4792ff868 chore(deps): bump prospector from 1.14.1 to 1.16.1
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.14.1 to 1.16.1.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.14.1...v1.16.1)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 11:59:15 +03:00
dependabot[bot]
82a6ac63bb chore(deps): bump coverage from 7.6.12 to 7.8.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.12 to 7.8.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.12...7.8.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 11:59:08 +03:00
dependabot[bot]
93d8285006 chore(deps): bump pytest-cov from 6.0.0 to 6.1.1
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 6.0.0 to 6.1.1.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.0.0...v6.1.1)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-23 11:58:59 +03:00
parul-aro
0115648a1d feat(cleanup): allow credentials in cleanup method 2025-04-23 11:54:17 +03:00
Mathieu Kniewallner
479a355d22 chore: explicitly require Python >= 3.9 2025-04-23 11:42:02 +03:00
Mathieu Kniewallner
fdf22fffba chore: explicitly require Django >= 4.2 2025-04-23 11:42:02 +03:00
Mathieu Kniewallner
682e4261c9 ci: exclude Python 3.13 for Django 4.2 2025-04-23 11:42:02 +03:00
Mathieu Kniewallner
133f19b2f5 chore: correctly test against Django 4.2 2025-04-23 11:42:02 +03:00
Mathieu Kniewallner
3e3da350ea chore: support Django 5.2 2025-04-23 11:42:02 +03:00
dependabot[bot]
ff9c3296ef chore(deps): bump prospector from 1.14.0 to 1.14.1
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.14.0 to 1.14.1.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/1.14.0...v1.14.1)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 13:17:31 +02:00
dependabot[bot]
6aedd78c0b chore(deps): bump pytest-django from 4.9.0 to 4.10.0
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.9.0 to 4.10.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.9.0...v4.10.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 13:17:23 +02:00
dependabot[bot]
be18f038f9 chore(deps): bump black from 24.10.0 to 25.1.0
Bumps [black](https://github.com/psf/black) from 24.10.0 to 25.1.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.10.0...25.1.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 13:17:15 +02:00
dependabot[bot]
04638811d7 chore(deps): bump coverage from 7.6.10 to 7.6.12
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.10 to 7.6.12.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.10...7.6.12)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-21 13:17:08 +02:00
Django Goat
a0df8ae7c4 docs - fixed wy-table-responsive html not not displaying 2025-02-21 13:16:56 +02:00
Django Goat
784f1930af docs - added css to increase content width 2025-02-21 13:16:56 +02:00
Aleksi Häkli
8e600536b1
Version 7.0.2 2025-02-19 19:50:54 +02:00
dependabot[bot]
d590dd6fb9 chore(deps): bump pytest from 8.3.3 to 8.3.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.3 to 8.3.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.3...8.3.4)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 19:50:25 +02:00
dependabot[bot]
0dab4d36cf chore(deps): bump mypy from 1.13.0 to 1.15.0
Bumps [mypy](https://github.com/python/mypy) from 1.13.0 to 1.15.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.13.0...v1.15.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 19:46:21 +02:00
dependabot[bot]
09145a8fc7 chore(deps): bump sphinx-rtd-theme from 3.0.1 to 3.0.2
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 3.0.1 to 3.0.2.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/3.0.1...3.0.2)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 19:46:13 +02:00
dependabot[bot]
4e21791ed6 chore(deps): bump pytest-cov from 5.0.0 to 6.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 5.0.0 to 6.0.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 19:46:04 +02:00
dependabot[bot]
c354217ee4 chore(deps): bump pytest-subtests from 0.13.1 to 0.14.1
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.13.1 to 0.14.1.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/v0.13.1...v0.14.1)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 18:11:11 +02:00
dependabot[bot]
6ea4879c55 chore(deps): bump coverage from 7.6.4 to 7.6.10
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.4 to 7.6.10.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.4...7.6.10)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 18:11:00 +02:00
dependabot[bot]
78de78261d chore(deps): bump tox from 4.23.2 to 4.24.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.23.2 to 4.24.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.23.2...4.24.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 18:10:48 +02:00
dependabot[bot]
8e0c2ec4b7 chore(deps): bump prospector from 1.12.1 to 1.14.0
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.12.1 to 1.14.0.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.12.1...1.14.0)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-19 18:10:37 +02:00
Andrew Neher
a0fd10da4c updated config docs for new AXES_USERNAME_FORM_FIELD default value. 2025-02-19 18:09:40 +02:00
Andrew Neher
2fb772efdb take out test-breaking modifications. My test custom user model wasn't working. 2025-02-19 18:09:40 +02:00
Andrew Neher
129e93cc0e savepoint 2025-02-19 18:09:40 +02:00
Jacobus-afk
ce3bfd51be Update 4_configuration.rst - aligned table pipe column 2025-02-19 18:08:17 +02:00
Aleksi Häkli
9a7673a47e Update Python support matrix
Deprecate support for Python 3.8
Add support for Python 3.13
2024-12-02 18:23:57 +02:00
dependabot[bot]
4c3a36cf9a chore(deps): bump sphinx-rtd-theme from 2.0.0 to 3.0.1
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 2.0.0 to 3.0.1.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/2.0.0...3.0.1)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:11:56 +02:00
dependabot[bot]
d17e4ecd4b chore(deps): bump mypy from 1.11.2 to 1.13.0
Bumps [mypy](https://github.com/python/mypy) from 1.11.2 to 1.13.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.2...v1.13.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:11:48 +02:00
dependabot[bot]
4511695e9f chore(deps): bump coverage from 7.6.1 to 7.6.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.1 to 7.6.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.1...7.6.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:11:36 +02:00
dependabot[bot]
43965514cb chore(deps): bump prospector from 1.10.3 to 1.12.1
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.10.3 to 1.12.1.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/prospector-dev/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.10.3...v1.12.1)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:10:30 +02:00
dependabot[bot]
01c32f051f chore(deps): bump black from 24.8.0 to 24.10.0
Bumps [black](https://github.com/psf/black) from 24.8.0 to 24.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.8.0...24.10.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:10:17 +02:00
dependabot[bot]
71bcfba42d chore(deps): bump tox from 4.21.1 to 4.23.2
Bumps [tox](https://github.com/tox-dev/tox) from 4.21.1 to 4.23.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.21.1...4.23.2)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-29 10:10:09 +02:00
Oscar van Leusen
67b94d0dfb Fix AXES_CLIENT_IP_CALLABLE error
Previously, the readme suggested the AXES_CLIENT_IP_CALLABLE function should have two arguments, but actually it only takes one argument.
2024-10-29 10:09:57 +02:00
dependabot[bot]
9acda1f892 chore(deps): bump tox from 4.21.0 to 4.21.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.21.0 to 4.21.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.21.0...4.21.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-03 17:25:14 +03:00
Bruno Alla
77ae2a2d14 docs: fix broken settings table 2024-10-03 13:10:01 +03:00
Aleksi Häkli
d52be951a1
Version 7.0.0 2024-10-02 20:33:18 +03:00
Aleksi Häkli
4e89d72b92
Version 7 breaking changes notes 2024-10-02 20:30:44 +03:00
Bruno Alla
b54019fa0f Change AXES_COOLOFF_TIME callable to take exactly 1 argument 2024-10-02 20:15:31 +03:00
Bruno Alla
8ed0d82384 refactor: remove attempt_time parameter
As we pass down the whole request, we no longer need to extract the axes_attempt_time anymore.

This is a potential breaking change, but the impacted functions are not part of the documented API.
2024-10-02 20:15:31 +03:00
Bruno Alla
a304380853 feat: pass down the request in a few more places 2024-10-02 20:15:31 +03:00
Bruno Alla
510c8d18f5 feat: pass the request to get dynamic cool off period 2024-10-02 20:15:31 +03:00
Bruno Alla
3f4526e8f5 chore: revert unrelated stylistic change 2024-10-02 20:15:31 +03:00
Bruno Alla
2fb4c81243 feat: pass username to AXES_COOLOFF_TIME callback
If the AXES_COOLOFF_TIME is a callable or path to a callable taking
an argument, pass the username to it.

This should enable users to customize the cool off to be user dependant,
and possibly implement a growing cool-off time:

- First lockout cools off after 5 mins
- Second one after 10 mins
- etc...
2024-10-02 20:15:31 +03:00
Aleksi Häkli
3fa7fce3ad
Merge pull request #1230 from jazzband/dependabot/pip/tox-4.21.0
chore(deps): bump tox from 4.20.0 to 4.21.0
2024-10-01 15:49:25 +03:00
dependabot[bot]
66fbadff06
chore(deps): bump tox from 4.20.0 to 4.21.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.20.0 to 4.21.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.20.0...4.21.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-01 12:30:43 +00:00
Aleksi Häkli
200351d574 Remove max-parallel limits for tests 2024-09-21 13:12:21 +03:00
Aleksi Häkli
dfa1eecfbf Version 6.5.2 2024-09-21 13:04:35 +03:00
Aleksi Häkli
3804e834a8 Drop PyPy support
Tests are failing and need updates to fully support PyPy 3.10.
If anyone needs PyPy support please feel free to fix the tox matrix
and open a PR that reintroduces PyPy support for the project.
2024-09-21 13:02:49 +03:00
Aleksi Häkli
0fb08fed86 Update test matrix
Run tests for Django 5.0 and 5.1
Run tests for Python 3.12
Add support for Django 5.1
Drop support for Django 3.2
2024-09-21 12:49:46 +03:00
Aleksi Häkli
f34ee54f98
Merge pull request #1224 from jazzband/dependabot/pip/mypy-1.11.2
chore(deps): bump mypy from 1.11.1 to 1.11.2
2024-09-21 11:33:27 +03:00
Aleksi Häkli
b685bfa80e
Merge pull request #1225 from jazzband/dependabot/pip/pytest-django-4.9.0
chore(deps): bump pytest-django from 4.8.0 to 4.9.0
2024-09-21 11:33:19 +03:00
Aleksi Häkli
a4e48ed004
Merge pull request #1227 from jazzband/dependabot/pip/pytest-8.3.3
chore(deps): bump pytest from 8.3.2 to 8.3.3
2024-09-21 11:33:12 +03:00
Aleksi Häkli
b00297c01a
Merge pull request #1229 from jazzband/dependabot/pip/tox-4.20.0
chore(deps): bump tox from 4.16.0 to 4.20.0
2024-09-21 11:33:03 +03:00
Aleksi Häkli
07483c6a15 Fix failing test case for new Django versions
See https://code.djangoproject.com/ticket/34968 for more details
2024-09-21 11:32:34 +03:00
dependabot[bot]
58498c9716
chore(deps): bump pytest-django from 4.8.0 to 4.9.0
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.8.0 to 4.9.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/main/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.8.0...v4.9.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 07:51:37 +00:00
dependabot[bot]
755c416198
chore(deps): bump pytest from 8.3.2 to 8.3.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.2 to 8.3.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.2...8.3.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 07:51:37 +00:00
dependabot[bot]
3a69c3ec3b
chore(deps): bump mypy from 1.11.1 to 1.11.2
Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 07:51:35 +00:00
Aleksi Häkli
9472011ed8
Merge pull request #1219 from jazzband/dependabot/pip/coverage-7.6.1
chore(deps): bump coverage from 7.6.0 to 7.6.1
2024-09-21 10:51:04 +03:00
Aleksi Häkli
574ab456d7
Merge pull request #1218 from jazzband/dependabot/pip/black-24.8.0
chore(deps): bump black from 24.4.2 to 24.8.0
2024-09-21 10:50:50 +03:00
dependabot[bot]
bfe66130fb
chore(deps): bump tox from 4.16.0 to 4.20.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.16.0 to 4.20.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.16.0...4.20.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-19 12:26:48 +00:00
dependabot[bot]
f0be0d3b8a
chore(deps): bump coverage from 7.6.0 to 7.6.1
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.6.0 to 7.6.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.6.0...7.6.1)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 12:22:47 +00:00
dependabot[bot]
9f350d52c0
chore(deps): bump black from 24.4.2 to 24.8.0
Bumps [black](https://github.com/psf/black) from 24.4.2 to 24.8.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.4.2...24.8.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 12:22:41 +00:00
Aleksi Häkli
2c4a4f6f8d
Merge pull request #1217 from jazzband/dependabot/pip/mypy-1.11.1
chore(deps): bump mypy from 1.11.0 to 1.11.1
2024-08-03 13:35:07 +03:00
dependabot[bot]
585d5a7bb9
chore(deps): bump mypy from 1.11.0 to 1.11.1
Bumps [mypy](https://github.com/python/mypy) from 1.11.0 to 1.11.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11...v1.11.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-31 12:21:51 +00:00
Aleksi Häkli
ef7e8467b4
Merge pull request #1213 from jazzband/dependabot/pip/pytest-subtests-0.13.1
chore(deps): bump pytest-subtests from 0.12.1 to 0.13.1
2024-07-28 15:22:01 +03:00
dependabot[bot]
dcc8274f87
chore(deps): bump pytest-subtests from 0.12.1 to 0.13.1
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.12.1 to 0.13.1.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/v0.12.1...v0.13.1)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 12:21:56 +00:00
Aleksi Häkli
0f6f62177d
Merge pull request #1212 from jazzband/dependabot/pip/coverage-7.6.0
chore(deps): bump coverage from 7.5.4 to 7.6.0
2024-07-28 15:21:49 +03:00
Aleksi Häkli
b4863515a9
Merge pull request #1214 from jazzband/dependabot/pip/mypy-1.11.0
chore(deps): bump mypy from 1.10.1 to 1.11.0
2024-07-28 15:21:34 +03:00
dependabot[bot]
afdacb2548
chore(deps): bump coverage from 7.5.4 to 7.6.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.5.4 to 7.6.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.5.4...7.6.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-28 12:21:32 +00:00
Aleksi Häkli
3161af86e6
Merge pull request #1210 from jazzband/dependabot/pip/tox-4.16.0
chore(deps): bump tox from 4.15.1 to 4.16.0
2024-07-28 15:20:57 +03:00
Aleksi Häkli
1e442d7d39
Merge pull request #1216 from jazzband/dependabot/pip/pytest-8.3.2
chore(deps): bump pytest from 8.2.2 to 8.3.2
2024-07-28 15:20:47 +03:00
dependabot[bot]
a70ea19015
chore(deps): bump pytest from 8.2.2 to 8.3.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.2 to 8.3.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.2.2...8.3.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-25 12:13:07 +00:00
dependabot[bot]
1637952978
chore(deps): bump mypy from 1.10.1 to 1.11.0
Bumps [mypy](https://github.com/python/mypy) from 1.10.1 to 1.11.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.1...v1.11)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 12:22:30 +00:00
dependabot[bot]
b737f719e7
chore(deps): bump tox from 4.15.1 to 4.16.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.15.1 to 4.16.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.15.1...4.16.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-03 12:07:59 +00:00
Aleksi Häkli
b2aec4377a
Update README.rst 2024-07-01 17:35:43 +03:00
Aleksi Häkli
1731602eb0
Version 6.5.1 2024-07-01 17:34:27 +03:00
Aleksi Häkli
2f142c4300
Merge pull request #1209 from hirotasoshu/fix_1191
fix: make 0007_alter_accessattempt_unique_together.py migration backwards compatible
2024-07-01 17:32:01 +03:00
Maksim Zayakin
6202062f91
fix: make 007 migration backwards compatible 2024-06-29 21:21:15 +05:00
Aleksi Häkli
aac7e67249
Merge pull request #1206 from jazzband/dependabot/pip/coverage-7.5.4
chore(deps): bump coverage from 7.5.3 to 7.5.4
2024-06-25 17:20:59 +03:00
Aleksi Häkli
e2f1e28c43
Merge pull request #1207 from jazzband/dependabot/pip/mypy-1.10.1
chore(deps): bump mypy from 1.10.0 to 1.10.1
2024-06-25 17:20:49 +03:00
dependabot[bot]
754c03a22d
chore(deps): bump mypy from 1.10.0 to 1.10.1
Bumps [mypy](https://github.com/python/mypy) from 1.10.0 to 1.10.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.10.0...v1.10.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-25 12:36:55 +00:00
dependabot[bot]
1a586c09f0
chore(deps): bump coverage from 7.5.3 to 7.5.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.5.3 to 7.5.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.5.3...7.5.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 12:02:07 +00:00
Aleksi Häkli
01431c5ee5
Version 6.5.0 2024-06-11 21:06:18 +03:00
Aleksi Häkli
b2db6fb6ba
Merge pull request #1202 from jazzband/dependabot/pip/coverage-7.5.3
chore(deps): bump coverage from 7.4.4 to 7.5.3
2024-06-11 21:04:57 +03:00
Aleksi Häkli
8724405016
Merge pull request #1198 from sevdog/access-log-identify-session
Add session hash to access log
2024-06-11 21:03:50 +03:00
dependabot[bot]
3386c85b24
chore(deps): bump coverage from 7.4.4 to 7.5.3
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.4 to 7.5.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.4...7.5.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-11 17:57:37 +00:00
Aleksi Häkli
f70138ef5e
Merge pull request #1205 from jazzband/dependabot/pip/tox-4.15.1
chore(deps): bump tox from 4.15.0 to 4.15.1
2024-06-11 20:56:59 +03:00
Aleksi Häkli
1ddfcfa9ff
Merge pull request #1204 from jazzband/dependabot/pip/pytest-8.2.2
chore(deps): bump pytest from 8.2.0 to 8.2.2
2024-06-11 20:56:47 +03:00
Aleksi Häkli
1f17ee0b11
Merge pull request #1195 from jazzband/dependabot/pip/black-24.4.2
chore(deps): bump black from 24.4.0 to 24.4.2
2024-06-11 20:56:40 +03:00
dependabot[bot]
a5a25b2cd4
chore(deps): bump tox from 4.15.0 to 4.15.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.15.0 to 4.15.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.15.0...4.15.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-06 12:45:03 +00:00
dependabot[bot]
324c4b3b70
chore(deps): bump pytest from 8.2.0 to 8.2.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.2.0 to 8.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.2.0...8.2.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-05 12:28:00 +00:00
dependabot[bot]
0965dfc874
chore(deps): bump black from 24.4.0 to 24.4.2
Bumps [black](https://github.com/psf/black) from 24.4.0 to 24.4.2.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.4.0...24.4.2)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 16:29:28 +00:00
Aleksi Häkli
4b34437294
Merge pull request #1197 from jazzband/dependabot/pip/pytest-8.2.0
chore(deps): bump pytest from 8.1.1 to 8.2.0
2024-05-07 19:28:48 +03:00
Aleksi Häkli
0a23b4130b
Merge pull request #1196 from jazzband/dependabot/pip/tox-4.15.0
chore(deps): bump tox from 4.14.2 to 4.15.0
2024-05-07 19:28:41 +03:00
Aleksi Häkli
830728e56c
Merge pull request #1194 from jazzband/dependabot/pip/mypy-1.10.0
chore(deps): bump mypy from 1.9.0 to 1.10.0
2024-05-07 19:28:28 +03:00
Davide
014483c65d
Add session hash to access log 2024-04-30 16:22:50 +02:00
dependabot[bot]
a2755bf4c8
chore(deps): bump pytest from 8.1.1 to 8.2.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.1 to 8.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.1.1...8.2.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 12:16:58 +00:00
dependabot[bot]
480ccde882
chore(deps): bump tox from 4.14.2 to 4.15.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.14.2 to 4.15.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.14.2...4.15.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 12:16:54 +00:00
dependabot[bot]
78bf8ffaf6
chore(deps): bump mypy from 1.9.0 to 1.10.0
Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-25 12:51:04 +00:00
Aleksi Häkli
ffe4fbf73d
Merge pull request #1189 from jazzband/dependabot/pip/black-24.4.0
chore(deps): bump black from 24.3.0 to 24.4.0
2024-04-15 20:35:51 +03:00
Aleksi Häkli
43b5017086
Merge pull request #1188 from Viicos/patch-1
Require `asgiref>=3.6.0`
2024-04-15 20:35:42 +03:00
dependabot[bot]
6d3cd021ac
chore(deps): bump black from 24.3.0 to 24.4.0
Bumps [black](https://github.com/psf/black) from 24.3.0 to 24.4.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.3.0...24.4.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 12:04:42 +00:00
Victorien
61c6dd9a08
Require asgiref>=3.6.0
`django-axes` is making use of some functions added in
`asgiref==3.6.0`, but Django 3.2 requires `asgiref>=3.3.2`.
2024-04-03 11:00:45 +02:00
Aleksi Häkli
fd9d185ad3
Merge pull request #1187 from jazzband/dependabot/pip/tox-4.14.2
chore(deps): bump tox from 4.14.1 to 4.14.2
2024-03-25 18:56:09 +02:00
Aleksi Häkli
5e01c6eced
Merge pull request #1186 from jazzband/dependabot/pip/pytest-cov-5.0.0
chore(deps): bump pytest-cov from 4.1.0 to 5.0.0
2024-03-25 18:56:03 +02:00
Aleksi Häkli
68c122855a
Merge pull request #1184 from jazzband/dependabot/pip/black-24.3.0
chore(deps): bump black from 24.2.0 to 24.3.0
2024-03-25 18:55:54 +02:00
dependabot[bot]
dabedb4d9f
chore(deps): bump tox from 4.14.1 to 4.14.2
Bumps [tox](https://github.com/tox-dev/tox) from 4.14.1 to 4.14.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.14.1...4.14.2)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 12:20:47 +00:00
dependabot[bot]
69d394029b
chore(deps): bump pytest-cov from 4.1.0 to 5.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.1.0 to 5.0.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...v5.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 12:20:35 +00:00
dependabot[bot]
462fa56c67
chore(deps): bump black from 24.2.0 to 24.3.0
Bumps [black](https://github.com/psf/black) from 24.2.0 to 24.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.2.0...24.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 12:18:20 +00:00
Aleksi Häkli
6e63f47a78 Revert "chore(deps): bump codecov/codecov-action from 3 to 4"
This reverts commit bba4c80989.
2024-03-16 12:54:27 +02:00
Aleksi Häkli
3e6c9ed0de
Merge pull request #1183 from jazzband/dependabot/pip/coverage-7.4.4
chore(deps): bump coverage from 7.4.3 to 7.4.4
2024-03-16 12:35:41 +02:00
dependabot[bot]
dba7d4ef6c
chore(deps): bump coverage from 7.4.3 to 7.4.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.3 to 7.4.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-15 12:58:22 +00:00
Aleksi Häkli
075b8d6f51
Merge pull request #1182 from jazzband/dependabot/pip/pytest-8.1.1
chore(deps): bump pytest from 8.1.0 to 8.1.1
2024-03-13 12:32:39 +02:00
Aleksi Häkli
6edb4d0f33
Merge pull request #1181 from jazzband/dependabot/pip/mypy-1.9.0
chore(deps): bump mypy from 1.8.0 to 1.9.0
2024-03-13 12:32:28 +02:00
dependabot[bot]
33b76271b8
chore(deps): bump pytest from 8.1.0 to 8.1.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.1.0 to 8.1.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.1.0...8.1.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 12:28:09 +00:00
dependabot[bot]
6fda722179
chore(deps): bump mypy from 1.8.0 to 1.9.0
Bumps [mypy](https://github.com/python/mypy) from 1.8.0 to 1.9.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.8.0...1.9.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 12:28:04 +00:00
Aleksi Häkli
82ec4b92a3
Merge pull request #1180 from jazzband/dependabot/pip/pytest-subtests-0.12.1
chore(deps): bump pytest-subtests from 0.11.0 to 0.12.1
2024-03-11 13:37:08 +02:00
Aleksi Häkli
3d547cd9db
Merge pull request #1179 from jazzband/dependabot/pip/tox-4.14.1
chore(deps): bump tox from 4.13.0 to 4.14.1
2024-03-11 13:36:47 +02:00
dependabot[bot]
d44631188c
chore(deps): bump pytest-subtests from 0.11.0 to 0.12.1
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.11.0 to 0.12.1.
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/v0.11.0...v0.12.1)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-08 12:29:58 +00:00
dependabot[bot]
61ce115eea
chore(deps): bump tox from 4.13.0 to 4.14.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.13.0 to 4.14.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.13.0...4.14.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-07 12:22:13 +00:00
Aleksi Häkli
6c54cf4e29
Version 6.4.0 2024-03-04 20:36:17 +02:00
Aleksi Häkli
3986334ce9
Merge pull request #1176 from jazzband/python-django-version-update
Add support for Python 3.12 and Django 5.0
2024-03-04 20:35:18 +02:00
Aleksi Häkli
a069a42b50 Drop pkg_resources from tests 2024-03-04 20:27:20 +02:00
Aleksi Häkli
0de5ab364c Add support for Python 3.12 and Django 5.0 2024-03-04 20:19:19 +02:00
Aleksi Häkli
048b1f9e99
Version 6.3.1 2024-03-04 20:18:31 +02:00
Aleksi Häkli
9d847e50bb
Merge pull request #1168 from jazzband/dependabot/pip/black-24.2.0
chore(deps): bump black from 23.12.1 to 24.2.0
2024-03-04 20:08:04 +02:00
Aleksi Häkli
dbd16dd5b0 Update syntax to be compliant with new black version 2024-03-04 20:07:47 +02:00
Aleksi Häkli
92f038e4af
Merge pull request #1169 from Viicos/remove-pkg-resources
Remove compatibility with `pkg_resources`
2024-03-04 19:52:32 +02:00
dependabot[bot]
c62f5d3198
chore(deps): bump black from 23.12.1 to 24.2.0
Bumps [black](https://github.com/psf/black) from 23.12.1 to 24.2.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.12.1...24.2.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 17:52:28 +00:00
Aleksi Häkli
ef552bcc62
Merge pull request #1174 from jazzband/dependabot/pip/coverage-7.4.3
chore(deps): bump coverage from 7.4.0 to 7.4.3
2024-03-04 19:51:42 +02:00
dependabot[bot]
d224dd11ad
chore(deps): bump coverage from 7.4.0 to 7.4.3
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.4.0 to 7.4.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.4.0...7.4.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 17:51:32 +00:00
Aleksi Häkli
12eca00d5c
Merge pull request #1158 from jazzband/dependabot/github_actions/actions/cache-4
chore(deps): bump actions/cache from 3 to 4
2024-03-04 19:51:06 +02:00
Aleksi Häkli
31e4a190b7
Merge pull request #1162 from cclauss/black-v24.1.0
chore(deps): bump black from 23.12.1 to 24.1.1 AGAIN
2024-03-04 19:51:00 +02:00
Aleksi Häkli
7b2b66c539
Merge pull request #1166 from jazzband/dependabot/pip/pytest-django-4.8.0
chore(deps): bump pytest-django from 4.7.0 to 4.8.0
2024-03-04 19:50:52 +02:00
Aleksi Häkli
875053a9a8
Merge pull request #1167 from jazzband/dependabot/github_actions/codecov/codecov-action-4
chore(deps): bump codecov/codecov-action from 3 to 4
2024-03-04 19:50:46 +02:00
Aleksi Häkli
00318b2bf9
Merge pull request #1171 from jazzband/dependabot/pip/tox-4.13.0
chore(deps): bump tox from 4.12.0 to 4.13.0
2024-03-04 19:50:41 +02:00
Aleksi Häkli
89ee887085
Merge pull request #1175 from jazzband/dependabot/pip/pytest-8.1.0
chore(deps): bump pytest from 7.4.4 to 8.1.0
2024-03-04 19:50:30 +02:00
dependabot[bot]
382e1b35af
chore(deps): bump pytest from 7.4.4 to 8.1.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.4 to 8.1.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.4...8.1.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 12:03:50 +00:00
dependabot[bot]
45c8119604
chore(deps): bump tox from 4.12.0 to 4.13.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.12.0 to 4.13.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.12.0...4.13.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 12:10:36 +00:00
Viicos
885ac8ddfb Remove setuptools dependency 2024-02-16 10:47:58 +01:00
Viicos
ab54d68ee1 Remove compatibility with pkg_resources 2024-02-15 21:39:35 +01:00
Christian Clauss
f9bb78ff4a
black=24.1.1 2024-02-03 16:15:41 +01:00
dependabot[bot]
bba4c80989
chore(deps): bump codecov/codecov-action from 3 to 4
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 12:42:06 +00:00
dependabot[bot]
b583c8d0ed
chore(deps): bump pytest-django from 4.7.0 to 4.8.0
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.7.0 to 4.8.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.7.0...v4.8.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-31 12:40:41 +00:00
Christian Clauss
493789640b chore(deps): bump black from 23.12.1 to 24.1.0 AGAIN 2024-01-26 21:37:26 +01:00
dependabot[bot]
ea95552533
chore(deps): bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-18 12:37:01 +00:00
Aleksi Häkli
448d0cea1a
Merge pull request #1156 from jazzband/dependabot/pip/tox-4.12.0
chore(deps): bump tox from 4.11.4 to 4.12.0
2024-01-13 21:00:55 +02:00
Aleksi Häkli
2a33b2dfd0
Merge pull request #1155 from jazzband/dependabot/pip/pytest-7.4.4
chore(deps): bump pytest from 7.4.3 to 7.4.4
2024-01-13 21:00:44 +02:00
dependabot[bot]
d1faa7c413
chore(deps): bump tox from 4.11.4 to 4.12.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.11.4 to 4.12.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.11.4...4.12.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 12:33:39 +00:00
dependabot[bot]
5d19320179
chore(deps): bump pytest from 7.4.3 to 7.4.4
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.3 to 7.4.4.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 12:57:42 +00:00
Aleksi Häkli
d858c12c1a
Merge pull request #1154 from jazzband/dependabot/pip/coverage-7.4.0
chore(deps): bump coverage from 7.3.4 to 7.4.0
2023-12-29 18:41:07 +02:00
dependabot[bot]
b3286ad177
chore(deps): bump coverage from 7.3.4 to 7.4.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.4 to 7.4.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.3.4...7.4.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-28 12:02:16 +00:00
Aleksi Häkli
87bdcbee3e
Version 6.3.0 2023-12-27 12:44:57 +02:00
Aleksi Häkli
02019db6ee
Merge pull request #1153 from jazzband/format
Fix code formatting
2023-12-27 12:41:30 +02:00
Aleksi Häkli
add22bd1de
Merge pull request #1151 from jazzband/dependabot/pip/mypy-1.8.0
chore(deps): bump mypy from 1.7.1 to 1.8.0
2023-12-27 12:41:21 +02:00
Aleksi Häkli
38e316ca87
Fix code formatting 2023-12-27 12:40:23 +02:00
Aleksi Häkli
95507857f0
Merge pull request #1147 from Taikono-Himazin/master
Add async support to middleware
2023-12-27 12:37:10 +02:00
dependabot[bot]
1aa273d99a
chore(deps): bump mypy from 1.7.1 to 1.8.0
Bumps [mypy](https://github.com/python/mypy) from 1.7.1 to 1.8.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-27 10:32:20 +00:00
Aleksi Häkli
55f9272827
Merge pull request #1150 from jazzband/dependabot/pip/coverage-7.3.4
chore(deps): bump coverage from 7.3.2 to 7.3.4
2023-12-27 12:31:36 +02:00
Aleksi Häkli
7fef7df8aa
Merge pull request #1149 from jazzband/dependabot/github_actions/github/codeql-action-3
chore(deps): bump github/codeql-action from 2 to 3
2023-12-27 12:31:27 +02:00
Aleksi Häkli
eee5a69a4a
Merge pull request #1152 from jazzband/dependabot/pip/black-23.12.1
chore(deps): bump black from 23.11.0 to 23.12.1
2023-12-27 12:31:13 +02:00
dependabot[bot]
1394e1aa27
chore(deps): bump black from 23.11.0 to 23.12.1
Bumps [black](https://github.com/psf/black) from 23.11.0 to 23.12.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.11.0...23.12.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 12:36:39 +00:00
dependabot[bot]
73d4b71ccb
chore(deps): bump coverage from 7.3.2 to 7.3.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.2 to 7.3.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.3.2...7.3.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-21 12:07:21 +00:00
dependabot[bot]
bd55542b80
chore(deps): bump github/codeql-action from 2 to 3
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 13:01:27 +00:00
Taikono-Himazin
6f2584b440 Add async support to middleware 2023-12-13 13:35:22 +09:00
Aleksi Häkli
2ee7ea1731
Version 6.2.0 2023-12-08 19:49:40 +02:00
Aleksi Häkli
25e824f666
Merge pull request #1142 from jazzband/dependabot/pip/tox-4.11.4
chore(deps): bump tox from 4.11.3 to 4.11.4
2023-12-08 19:43:50 +02:00
Aleksi Häkli
2a0fd0cfad
Merge pull request #1138 from p-l-/command-reset-ip-username
Add new management command `axes_reset_ip_username`
2023-12-08 19:39:40 +02:00
dependabot[bot]
2416fcc358
chore(deps): bump tox from 4.11.3 to 4.11.4
Bumps [tox](https://github.com/tox-dev/tox) from 4.11.3 to 4.11.4.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.11.3...4.11.4)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-08 17:32:42 +00:00
Aleksi Häkli
73e29645a0
Merge pull request #1145 from jazzband/dependabot/github_actions/actions/setup-python-5
chore(deps): bump actions/setup-python from 4 to 5
2023-12-08 19:31:58 +02:00
Aleksi Häkli
6aaaf23b27
Merge pull request #1141 from jazzband/dependabot/pip/sphinx-rtd-theme-2.0.0
chore(deps): bump sphinx-rtd-theme from 1.3.0 to 2.0.0
2023-12-08 19:31:44 +02:00
Aleksi Häkli
81335e1da0
Merge pull request #1143 from jazzband/funkybob-patch-1
Update 2_installation.rst
2023-12-08 19:31:35 +02:00
dependabot[bot]
aafc24e479
chore(deps): bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 12:24:36 +00:00
Curtis Maloney
d2b65aad32
Update 2_installation.rst
Fix block indentation in Installation documentation.
2023-12-01 11:59:19 +11:00
Pierre Lalet
6e64c62fbf Add new management command axes_reset_ip_username 2023-11-28 20:37:39 +01:00
dependabot[bot]
45d61b21ae
chore(deps): bump sphinx-rtd-theme from 1.3.0 to 2.0.0
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.3.0 to 2.0.0.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.3.0...2.0.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 12:20:14 +00:00
Aleksi Häkli
c9556f9783
Merge pull request #1140 from jazzband/dependabot/pip/mypy-1.7.1
chore(deps): bump mypy from 1.7.0 to 1.7.1
2023-11-27 19:13:37 +02:00
dependabot[bot]
219b465bc2
chore(deps): bump mypy from 1.7.0 to 1.7.1
Bumps [mypy](https://github.com/python/mypy) from 1.7.0 to 1.7.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-24 12:19:32 +00:00
Aleksi Häkli
6f688acb0f
Merge pull request #1136 from jazzband/dependabot/pip/black-23.11.0
chore(deps): bump black from 23.10.1 to 23.11.0
2023-11-22 21:11:02 +02:00
Aleksi Häkli
a9f58ef150
Merge pull request #1131 from jazzband/dependabot/pip/pytest-7.4.3
chore(deps): bump pytest from 7.4.2 to 7.4.3
2023-11-22 21:10:50 +02:00
dependabot[bot]
6b175c2c20
chore(deps): bump black from 23.10.1 to 23.11.0
Bumps [black](https://github.com/psf/black) from 23.10.1 to 23.11.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.10.1...23.11.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 19:03:08 +00:00
Aleksi Häkli
033ea906ef
Merge pull request #1134 from laulaz/master
Add French translations
2023-11-22 21:02:01 +02:00
dependabot[bot]
846b71ea01
chore(deps): bump pytest from 7.4.2 to 7.4.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.2 to 7.4.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.2...7.4.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 19:01:38 +00:00
Aleksi Häkli
bd709ec4a6
Merge pull request #1135 from jazzband/dependabot/pip/pytest-django-4.7.0
chore(deps): bump pytest-django from 4.5.2 to 4.7.0
2023-11-22 21:00:48 +02:00
Aleksi Häkli
10abb732d6
Merge pull request #1137 from jazzband/dependabot/pip/mypy-1.7.0
chore(deps): bump mypy from 1.6.1 to 1.7.0
2023-11-22 21:00:31 +02:00
dependabot[bot]
ac0a419a27
chore(deps): bump mypy from 1.6.1 to 1.7.0
Bumps [mypy](https://github.com/python/mypy) from 1.6.1 to 1.7.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 12:04:58 +00:00
dependabot[bot]
95d46c638f
chore(deps): bump pytest-django from 4.5.2 to 4.7.0
Bumps [pytest-django](https://github.com/pytest-dev/pytest-django) from 4.5.2 to 4.7.0.
- [Release notes](https://github.com/pytest-dev/pytest-django/releases)
- [Changelog](https://github.com/pytest-dev/pytest-django/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-django/compare/v4.5.2...v4.7.0)

---
updated-dependencies:
- dependency-name: pytest-django
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-08 12:51:46 +00:00
Laurent Lasudry
f64c1526d7 Add French translations 2023-11-06 12:35:54 +01:00
Aleksi Häkli
efe08b7d8b
Merge pull request #1130 from jazzband/dependabot/pip/black-23.10.1
chore(deps): bump black from 23.10.0 to 23.10.1
2023-10-24 17:40:18 +03:00
dependabot[bot]
734117c432
chore(deps): bump black from 23.10.0 to 23.10.1
Bumps [black](https://github.com/psf/black) from 23.10.0 to 23.10.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.10.0...23.10.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-24 12:46:39 +00:00
Aleksi Häkli
52eaa112dc
Merge pull request #1124 from jazzband/dependabot/pip/coverage-7.3.2
chore(deps): bump coverage from 7.3.0 to 7.3.2
2023-10-19 17:33:57 +03:00
dependabot[bot]
f65b74121e chore(deps): bump mypy from 1.5.1 to 1.6.1
Bumps [mypy](https://github.com/python/mypy) from 1.5.1 to 1.6.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.5.1...v1.6.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 17:33:07 +03:00
dependabot[bot]
6d136ee6e6 chore(deps): bump black from 23.9.1 to 23.10.0
Bumps [black](https://github.com/psf/black) from 23.9.1 to 23.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.9.1...23.10.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 17:32:19 +03:00
dependabot[bot]
6d039adb31 chore(deps): bump prospector from 1.10.2 to 1.10.3
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/landscapeio/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/1.10.2...v1.10.3)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 17:32:03 +03:00
Christian Bundy
eb7b703a70 Avoid running data migration on incorrect databases 2023-10-08 22:33:21 +03:00
Aleksi Häkli
da9c4f6d71
Merge branch 'master' into dependabot/pip/coverage-7.3.2 2023-10-08 22:31:29 +03:00
dependabot[bot]
36f828b4f5 chore(deps): bump black from 23.7.0 to 23.9.1
Bumps [black](https://github.com/psf/black) from 23.7.0 to 23.9.1.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.7.0...23.9.1)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-08 22:31:08 +03:00
dependabot[bot]
c57bac8aa2 chore(deps): bump tox from 4.11.0 to 4.11.3
Bumps [tox](https://github.com/tox-dev/tox) from 4.11.0 to 4.11.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.11.0...4.11.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-08 22:31:01 +03:00
dependabot[bot]
b5f464ef96 chore(deps): bump pytest from 7.4.0 to 7.4.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.0 to 7.4.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.4.0...7.4.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-08 22:30:55 +03:00
dependabot[bot]
28849e0e61 chore(deps): bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-08 22:30:46 +03:00
dependabot[bot]
c007839e30
chore(deps): bump coverage from 7.3.0 to 7.3.2
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.3.0 to 7.3.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.3.0...7.3.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-03 12:15:41 +00:00
Aleksi Häkli
e781d67514
Fix rST syntax 2023-09-01 10:54:43 +03:00
Aleksi Häkli
48bda54c30
Version 6.1.1 2023-09-01 10:54:03 +03:00
Maksim Zayakin
4861f3c988 fix: fix TransactionManagementError 2023-09-01 10:50:57 +03:00
dependabot[bot]
5f52f4383f chore(deps): bump tox from 4.10.0 to 4.11.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.10.0 to 4.11.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.10.0...4.11.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-30 19:28:06 +03:00
dependabot[bot]
fa8d059977 chore(deps): bump tox from 4.9.0 to 4.10.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.9.0 to 4.10.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.9.0...4.10.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-25 18:52:23 +03:00
dependabot[bot]
f6d5aa043d chore(deps): bump sphinx-rtd-theme from 1.2.2 to 1.3.0
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.2 to 1.3.0.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.2...1.3.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-25 18:35:14 +03:00
dependabot[bot]
fe4aa82ca1 chore(deps): bump tox from 4.8.0 to 4.9.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.8.0 to 4.9.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.8.0...4.9.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-19 23:26:32 +03:00
dependabot[bot]
9861ac6404 chore(deps): bump mypy from 1.5.0 to 1.5.1
Bumps [mypy](https://github.com/python/mypy) from 1.5.0 to 1.5.1.
- [Commits](https://github.com/python/mypy/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-19 23:26:17 +03:00
Ronny Vedrilla
b14e586f16 #1093: Setting proper headline (h2) for GDPR section 2023-08-19 23:25:23 +03:00
Aleksi Häkli
976399e30a
Clean up RTD build spec
Remove unnecessary comments
2023-08-17 12:42:58 +03:00
Aleksi Häkli
3e54945ef5
Revert "Downgrade black to 23.3.0 to fix RTD build"
This reverts commit 7d1ae3bca2.
2023-08-17 12:40:52 +03:00
Aleksi Häkli
c4fa363398
Revert "Downgrade mypy to 1.4.1 to fix RTD build"
This reverts commit c1884d257a.
2023-08-17 12:40:46 +03:00
Aleksi Häkli
6d88064e24
Add doc requirements 2023-08-17 12:35:11 +03:00
Aleksi Häkli
0688526722
Enable PDF and epub doc formats 2023-08-17 12:35:06 +03:00
Aleksi Häkli
16cc250622
Add example RTD build configuration 2023-08-17 12:32:44 +03:00
Aleksi Häkli
c1884d257a
Downgrade mypy to 1.4.1 to fix RTD build 2023-08-17 12:29:16 +03:00
Aleksi Häkli
7d1ae3bca2
Downgrade black to 23.3.0 to fix RTD build 2023-08-17 12:13:17 +03:00
dependabot[bot]
2e9b3e485c chore(deps): bump tox from 4.6.4 to 4.8.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.4 to 4.8.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.4...4.8.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-17 12:03:37 +03:00
dependabot[bot]
c80c812296 chore(deps): bump coverage from 7.2.7 to 7.3.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.7 to 7.3.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.7...7.3.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-17 12:03:26 +03:00
dependabot[bot]
634d54ba3d chore(deps): bump mypy from 1.4.1 to 1.5.0
Bumps [mypy](https://github.com/python/mypy) from 1.4.1 to 1.5.0.
- [Commits](https://github.com/python/mypy/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-17 12:03:19 +03:00
Aleksi Häkli
61da8fbdfc
Version 6.1.0 2023-07-30 19:03:18 +03:00
Ronny Vedrilla
e9db2d9631 #1093: "AXES_SENSITIVE_PARAMETERS" now have sensible defaults to follow "privacy-by-design" 2023-07-30 18:59:48 +03:00
Ron
ab78724518 Typo fix 2023-07-30 18:59:01 +03:00
Ron
c234f53e0d Changes and typo fix 2023-07-30 18:59:01 +03:00
Ronny Vedrilla
72f3be394d #438: Added docs about GDPR best practice 2023-07-30 18:59:01 +03:00
Maksim Zayakin
9a54187a65 chore: update docstrings about settings.AXES_COOLOFF_TIME 2023-07-25 19:57:20 +03:00
Maksim Zayakin
ebe1b74925 test: fix duplicate test names in AxesCoolOffTestCase 2023-07-25 19:57:20 +03:00
Maksim Zayakin
b5a3e7ddf5 fix: rename ISSUE_TEMPLATES to ISSUE_TEMPLATE (typo) 2023-07-21 21:28:53 +03:00
dependabot[bot]
cf8ffdda60 chore(deps): bump black from 23.3.0 to 23.7.0
Bumps [black](https://github.com/psf/black) from 23.3.0 to 23.7.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.3.0...23.7.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-18 12:47:29 +03:00
dependabot[bot]
1d4e1e3252 chore(deps): bump tox from 4.6.3 to 4.6.4
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.3 to 4.6.4.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.3...4.6.4)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-18 12:47:22 +03:00
Aleksi Häkli
1c2a998a78
Version 6.0.5 2023-07-01 00:06:22 +03:00
Kira
370fadad36 add indonesian translation 2023-07-01 00:05:22 +03:00
dependabot[bot]
ab79e05692 chore(deps): bump mypy from 1.4.0 to 1.4.1
Bumps [mypy](https://github.com/python/mypy) from 1.4.0 to 1.4.1.
- [Commits](https://github.com/python/mypy/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-28 12:29:10 +03:00
dependabot[bot]
f40d477ee7 chore(deps): bump pytest from 7.3.2 to 7.4.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.2 to 7.4.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.3.2...7.4.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-25 15:20:29 +03:00
Aleksi Häkli
51971ba423
Version 6.0.4 2023-06-22 11:46:36 +03:00
dependabot[bot]
6dee6352e2 chore(deps): bump mypy from 1.3.0 to 1.4.0
Bumps [mypy](https://github.com/python/mypy) from 1.3.0 to 1.4.0.
- [Commits](https://github.com/python/mypy/compare/v1.3.0...v1.4.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-22 11:45:07 +03:00
Pieter Goetschalckx
21cc0234de Test request after force_login 2023-06-22 11:44:53 +03:00
Pieter Goetschalckx
74f7670b63 Remove unused methods from AxesStandaloneBackend 2023-06-22 11:44:53 +03:00
dependabot[bot]
a2558c9f84 chore(deps): bump tox from 4.6.2 to 4.6.3
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.2 to 4.6.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.2...4.6.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-21 15:03:58 +03:00
dependabot[bot]
d871675b8e chore(deps): bump tox from 4.6.1 to 4.6.2
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.1 to 4.6.2.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.1...4.6.2)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 20:17:39 +03:00
Aleksi Häkli
1548a589dc
Version 6.0.3 2023-06-18 14:16:54 +03:00
Davide
deb0e330ad Add username to admin fieldsets #1073 2023-06-18 14:15:53 +03:00
dependabot[bot]
091d666677 chore(deps): bump tox from 4.6.0 to 4.6.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.6.0...4.6.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-18 14:15:38 +03:00
Aleksi Häkli
81360d017d
Version 6.0.2 2023-06-13 19:22:47 +03:00
dependabot[bot]
1e6ca7af86 chore(deps): bump pytest-cov from 4.0.0 to 4.1.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:17:31 +03:00
dependabot[bot]
f339b4cc1a chore(deps): bump tox from 4.5.1 to 4.6.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.5.1 to 4.6.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.5.1...4.6.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:17:19 +03:00
dependabot[bot]
36b6c2c36e chore(deps): bump coverage from 7.2.5 to 7.2.7
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.5 to 7.2.7.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.5...7.2.7)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:17:11 +03:00
dependabot[bot]
90ede53472 chore(deps): bump prospector from 1.10.0 to 1.10.2
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.10.0 to 1.10.2.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/PyCQA/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/1.10.0...1.10.2)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:16:22 +03:00
dependabot[bot]
8178a63bf7 chore(deps): bump sphinx-rtd-theme from 1.2.0 to 1.2.2
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.0 to 1.2.2.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.0...1.2.2)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:16:09 +03:00
dependabot[bot]
75a0709c6f chore(deps): bump pytest from 7.3.1 to 7.3.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.1 to 7.3.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.3.1...7.3.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:16:05 +03:00
Ian Fisher
5b235b50ed Add check for callable settings 2023-06-13 19:15:07 +03:00
Maksim Zayakin
498a691e5d chore: add pull request template 2023-06-13 19:09:57 +03:00
Maksim Zayakin
b8afdf13cf chore: add feature request template 2023-06-13 19:09:57 +03:00
Maksim Zayakin
ba96c12507 chore: add bug report template 2023-06-13 19:09:57 +03:00
Maksim Zayakin
c228ae1891 chore: fix typo 2023-06-13 19:09:17 +03:00
Aleksi Häkli
fcdeb4ae3f
Update release.yml
Use repository-url instead of repository_url 
as parameter name for the PyPI release task
2023-05-17 20:51:51 +03:00
Aleksi Häkli
d4cbdf7010
Version 6.0.1 2023-05-17 20:46:44 +03:00
Aleksi Häkli
1be420b95c
Update release.yml
Use stable v1 PyPI release task
2023-05-17 20:45:50 +03:00
Aleksi Häkli
10d1c60027
Allow Python >= 3.7 for RTD compatibility
Documentation autobuilder fails if Python 3.7
is not permitted for package installations in RTD
2023-05-17 20:39:56 +03:00
Aleksi Häkli
84e5e357e6 Version 6 2023-05-17 20:33:11 +03:00
Aleksi Häkli
33cfcb9656 Drop Python 3.7 support 2023-05-17 20:33:11 +03:00
dependabot[bot]
d463787e34 chore(deps): bump pytest-subtests from 0.10.0 to 0.11.0
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.10.0 to 0.11.0.
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-16 00:31:42 +03:00
Aleksi Häkli
1965d2f2ba
Update CHANGES.rst
Version 6.0.0b5
2023-05-15 00:42:13 +03:00
Maksim Zayakin
58d5b491d8 test: add deprecation check for is_admin_site, add is_admin_request tests 2023-05-14 23:01:58 +03:00
Maksim Zayakin
117db49091 feat: add is_admin_request, deprecate is_admin_site 2023-05-14 23:01:58 +03:00
Aleksi Häkli
102cdc3ecf
Fix typo 2023-05-13 14:52:37 +03:00
Aleksi Häkli
1e7e85e101
Improve documentation 2023-05-13 14:43:20 +03:00
dependabot[bot]
fe130af344
Bump mypy from 1.2.0 to 1.3.0
Bumps [mypy](https://github.com/python/mypy) from 1.2.0 to 1.3.0.
- [Commits](https://github.com/python/mypy/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Aleksi Häkli <aleksi.hakli@iki.fi>
2023-05-13 14:27:28 +03:00
Aleksi Häkli
3e13c8f85e
Version 6.0.0b4 2023-05-13 14:09:12 +03:00
Maksim Zayakin
f6e272cb61 docs: add AXES_LOCKOUT_PARAMETERS` entires 2023-05-13 14:04:14 +03:00
Maksim Zayakin
3cba78a599 refactor: fix qa errors 2023-05-13 14:04:14 +03:00
Maksim Zayakin
c66bb1fb2b test: add new client parameters tests 2023-05-13 14:04:14 +03:00
Maksim Zayakin
166c431b25 test: add callable AXES_LOCKOUT_PARAMETERS test cases and tests with wrong lockout parameters 2023-05-13 14:04:14 +03:00
Maksim Zayakin
43cc1ef39e test: add new login tests 2023-05-13 14:04:14 +03:00
Maksim Zayakin
0823584b02 refactor: fix prospector errors 2023-05-13 14:04:14 +03:00
Maksim Zayakin
73c4e4501b chore: adjust types: use list instead of iterables and nested lists instead of tuples 2023-05-13 14:04:14 +03:00
Maksim Zayakin
d5c6073bd7 test: use AXES_LOCKOUT_PARAMETERS in login test 2023-05-13 14:04:14 +03:00
Maksim Zayakin
a12c8bd6da test: use AXES_LOCKOUT_PARAMETERS in logging test 2023-05-13 14:04:14 +03:00
Maksim Zayakin
7ee8573939 test: use AXES_LOCKOUT_PARAMETERS in helpers test 2023-05-13 14:04:14 +03:00
Maksim Zayakin
7cb0144770 test: use AXES_LOCKOUT_PARAMETERS in handlers test 2023-05-13 14:04:14 +03:00
Maksim Zayakin
c3586ac3dc test: use AXES_LOCKOUT_PARAMETERS in attempts test 2023-05-13 14:04:14 +03:00
Maksim Zayakin
8d4a0aa052 chore!: pass request and credentials to get_client_parameters 2023-05-13 14:04:14 +03:00
Maksim Zayakin
1e3d41228d feat!: implement reset_request using get_lockout_parameters 2023-05-13 14:04:14 +03:00
Maksim Zayakin
476d3f52bc chore: use get_lockout_parameters in handlers 2023-05-13 14:04:14 +03:00
Maksim Zayakin
07539ff3d7 chore!: use AXES_LOCKOUT_PARAMETERS in app init log message 2023-05-13 14:04:14 +03:00
Maksim Zayakin
2df1c1948a refactor: refactor get_client_parameters
* Now accepts request_or_attempt and credentials which are passed to get_lockout_parameters
* Use lockout parameters that consumed from get_lockout_parameters
2023-05-13 14:04:14 +03:00
Maksim Zayakin
59a57386c1 feat: add get_lockout_parameters
Accepts request or AccessAttempt and optionally credentials. If AXES_LOCKOUT_PARAMETERS is callable, this function passes request or attempt and credentials to this callable and returns the result.
If AXES_LOCKOUT_PARAMETERS is iterable, returns AXES_LOCKOUT_PARAMETERS.
Otherwise raises TypeError.
2023-05-13 14:04:14 +03:00
Maksim Zayakin
97022a460d chore!: deprecate params related to lockout parameters resolution 2023-05-13 14:04:14 +03:00
Maksim Zayakin
ffc161e814 feat!: add AXES_LOCKOUT_PARAMETERS flag
BREAKING CHANGE: add `AXES_LOCKOUT_PARAMETERS` flag which accepts an iterable of keys or a callable that resolves an iterable.
The key can be a string (represents a single parameter) or an Iterable of strings (represents a combined parameter).
For example, using this parameters
```python
AXES_LOCKOUT_PARAMETERS = [
    "ip_address",
    ("username", "user_agent"),
]
```
axes will block users by IP and/or combination of username and user agent
2023-05-13 14:04:14 +03:00
Maksim Zayakin
9dfea8bf8e chore: fix inaccuracies in russian locale 2023-05-13 13:53:57 +03:00
dependabot[bot]
79660a7cc2 Bump prospector from 1.9.0 to 1.10.0
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/PyCQA/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.9.0...1.10.0)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-13 13:45:45 +03:00
Aleksi Häkli
b985981d11
Update supported Django versions specifiers 2023-05-01 21:39:04 +03:00
Aleksi Häkli
ad2f21a856
Revert language change 2023-05-01 21:34:33 +03:00
Aleksi Häkli
9b7b1e6aac
Version 6.0.0b3 2023-05-01 21:31:16 +03:00
Aleksi Häkli
8386f30dbb Fix type annotations for cache utilities 2023-05-01 21:27:47 +03:00
Aleksi Häkli
c3cfb5150a Refactor cache backend to use cache.incr method for request tracking
The old cache.set method has problems with correctness as well as
performance on higher traffic sites where there are
multiple parallel web servers running at the same time
which can overwrite each others shared cache
2023-05-01 21:27:47 +03:00
Aleksi Häkli
9924077a2a Rename get_cache_key to get_cache_keys 2023-05-01 21:27:47 +03:00
dependabot[bot]
feca1ed6d4 Bump coverage from 7.2.4 to 7.2.5
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.4 to 7.2.5.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.4...7.2.5)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 17:55:02 +03:00
dependabot[bot]
9b3a75cdbb Bump coverage from 7.2.3 to 7.2.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.3 to 7.2.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.3...7.2.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-28 16:21:28 +03:00
Aleksi Häkli
63c57c898b
Correct CHANGES word ordering 2023-04-28 14:28:51 +03:00
Aleksi Häkli
107b9b8508
Update CHANGES.rst 2023-04-28 14:23:59 +03:00
Aleksi Häkli
31249a5947 Update django-ipware configuration flags to new AXES_IPWARE_ prefixes
Use explicit new AXES_IPWARE_ referencing configuration flag names
in place of the old plain implicit AXES_ name prefixes
2023-04-28 14:16:44 +03:00
Aleksi Häkli
cd950ddfef Make ipware an optional dependency
Relates to #1038
2023-04-28 14:16:44 +03:00
Aleksi Häkli
700ea46607
Adjust GitHub and tox matrix to match current version support state 2023-04-28 13:08:26 +03:00
Aleksi Häkli
1d13338cdb
Drop PyPy 3.8 outcome ignores from test matrix 2023-04-28 12:31:10 +03:00
Aleksi Häkli
d12ee8270c
Drop Django 4.1 support from test matrix 2023-04-28 12:31:10 +03:00
Aleksi Häkli
f13475803b
Add Django 4.2 support to test matrix 2023-04-28 12:31:10 +03:00
dependabot[bot]
f376ab6c7f Bump tox from 4.5.0 to 4.5.1
Bumps [tox](https://github.com/tox-dev/tox) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.5.0...4.5.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-27 22:05:08 +03:00
Maksim Zayakin
ced022adfd docs: fix divider line after contributing section 2023-04-27 22:04:55 +03:00
Maksim Zayakin
8c9b753430 docs: rename 9_development.rst to 9_contributing.rst 2023-04-27 22:04:55 +03:00
Maksim Zayakin
3c1bb683ff docs: fix link to CONTRIBUTING in README 2023-04-27 22:04:55 +03:00
Maksim Zayakin
833ef9669f docs: add CONTRIBUTING.rst 2023-04-27 22:04:55 +03:00
Maksim Zayakin
128b5c6e25 chore: add venv to .gitignore
Add all environment related things, copy from https://github.com/github/gitignore/blob/main/Python.gitignore
2023-04-26 18:27:33 +03:00
Aleksi Häkli
f491f3dea8
Version 6.0.0b1 2023-04-25 19:44:29 +03:00
Maksim Zayakin
5fb675b588 test: change status code to 429 2023-04-25 19:42:37 +03:00
Maksim Zayakin
a4806ba6b4 docs: update AXES_HTTP_RESPONSE_CODE entry in configuration 2023-04-25 19:42:37 +03:00
Maksim Zayakin
8294fdf756 feat!: set default response code to 429 2023-04-25 19:42:37 +03:00
dependabot[bot]
c036149c6c Bump tox from 4.4.12 to 4.5.0
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.12 to 4.5.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.12...4.5.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-25 19:41:02 +03:00
Joe Wesch
6aee1cb8e7 Adds Django Auth LDAP as compatible 2023-04-19 11:39:02 +03:00
dependabot[bot]
c1175982e8
Bump pytest from 7.3.0 to 7.3.1 (#1034)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.3.0 to 7.3.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.3.0...7.3.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-17 16:38:55 +03:30
dependabot[bot]
498aeac86d Bump tox from 4.4.11 to 4.4.12
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.11 to 4.4.12.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.11...4.4.12)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-16 19:54:00 +03:00
Aleksi Häkli
8b9474079f
Version 5.41.1 2023-04-16 19:53:06 +03:00
ArtemDemidovAramMeem
0e76956a2e Override log handler when using sensitive parameters. Closes #1010 2023-04-16 19:51:10 +03:00
dependabot[bot]
d3fc47b05b Bump pytest from 7.2.2 to 7.3.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.2 to 7.3.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.2.2...7.3.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 18:26:32 +03:00
dependabot[bot]
632cd43222 Bump mypy from 1.1.1 to 1.2.0
Bumps [mypy](https://github.com/python/mypy) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-08 20:04:35 +03:00
dependabot[bot]
bad728bee8 Bump coverage from 7.2.2 to 7.2.3
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.2 to 7.2.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.2...7.2.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-08 20:04:30 +03:00
dependabot[bot]
15dbdde903 Bump tox from 4.4.8 to 4.4.11
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.8 to 4.4.11.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.8...4.4.11)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-08 20:04:22 +03:00
Aleksi Häkli
e56f4c9271
Version 5.41.0 2023-04-02 20:58:58 +03:00
Maksim Zayakin
25f6903899 fix logout tests 2023-04-02 19:34:25 +03:00
dependabot[bot]
6f629f64c9 Bump black from 23.1.0 to 23.3.0
Bumps [black](https://github.com/psf/black) from 23.1.0 to 23.3.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/23.1.0...23.3.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-01 13:39:46 +03:00
Maksim Zayakin
791cce011d add ClientIpAddressTestCase 2023-03-29 16:35:06 +03:00
Maksim Zayakin
91bc75f4a0 add info to docs about AXES_CLIENT_IP_CALLABLE 2023-03-29 16:35:06 +03:00
Maksim Zayakin
c8bfbe603a use AXES_CLIENT_IP_CALLABLE in get_client_ip_address 2023-03-29 16:35:06 +03:00
Maksim Zayakin
7ae0af2a1e add AXES_CLIENT_IP_CALLABLE setting 2023-03-29 16:35:06 +03:00
dependabot[bot]
dccbba7764 Bump tox from 4.4.7 to 4.4.8
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.7 to 4.4.8.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.7...4.4.8)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-29 10:32:10 +03:00
dependabot[bot]
55bf295686
Bump coverage from 7.2.1 to 7.2.2 (#1018)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.1...7.2.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-17 14:45:18 +01:00
dependabot[bot]
1d23bab33b
Bump tox from 4.4.6 to 4.4.7 (#1017)
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.6 to 4.4.7.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.6...4.4.7)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-13 14:14:19 +01:00
dependabot[bot]
21c468f8e0
Bump mypy from 1.0.0 to 1.1.1 (#1014)
Bumps [mypy](https://github.com/python/mypy) from 1.0.0 to 1.1.1.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v1.0.0...v1.1.1)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-07 14:11:01 +01:00
dependabot[bot]
068f5f8b9d
Bump pytest from 7.2.1 to 7.2.2 (#1013)
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.2.1...7.2.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-06 14:21:20 +01:00
dependabot[bot]
d661c32d77 Bump coverage from 7.2.0 to 7.2.1
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.2.0 to 7.2.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.2.0...7.2.1)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 10:37:11 +02:00
dependabot[bot]
c98d09f67a Bump tox from 4.4.4 to 4.4.6
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.4 to 4.4.6.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.4...4.4.6)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 10:36:57 +02:00
Hasan Ramezani
274c4f7010 Fix black formatting in migrations 2023-03-01 10:36:29 +02:00
dependabot[bot]
42002b3f2c Bump black from 22.12.0 to 23.1.0
Bumps [black](https://github.com/psf/black) from 22.12.0 to 23.1.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.12.0...23.1.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-26 14:00:35 +02:00
dependabot[bot]
4cc2558611 Bump prospector from 1.8.4 to 1.9.0
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/PyCQA/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-26 14:00:10 +02:00
dependabot[bot]
b00d1ffe21 Bump pytest-subtests from 0.9.0 to 0.10.0
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.9.0 to 0.10.0.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/0.9.0...0.10.0)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-26 14:00:03 +02:00
dependabot[bot]
64be7f95b3 Bump coverage from 7.1.0 to 7.2.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.1.0 to 7.2.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.1.0...7.2.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-26 13:59:50 +02:00
Hasan Ramezani
779fdf496b Adopt test based on Django security release for CVE-2023-24580 2023-02-26 13:59:27 +02:00
dependabot[bot]
7f534c0e3b
Bump sphinx-rtd-theme from 1.1.1 to 1.2.0 (#1000)
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.1.1 to 1.2.0.
- [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases)
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.1.1...1.2.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-16 14:03:51 +01:00
dependabot[bot]
120df25023
Bump mypy from 0.991 to 1.0.0 (#999)
Bumps [mypy](https://github.com/python/mypy) from 0.991 to 1.0.0.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.991...v1.0.0)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-08 08:49:04 +03:00
dependabot[bot]
986eda365e
Bump tox from 4.4.3 to 4.4.4 (#996)
Bumps [tox](https://github.com/tox-dev/tox) from 4.4.3 to 4.4.4.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.4.3...4.4.4)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-01 13:31:49 +01:00
dependabot[bot]
6d9c2bf1da
Bump tox from 4.3.5 to 4.4.3 (#995)
Bumps [tox](https://github.com/tox-dev/tox) from 4.3.5 to 4.4.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.3.5...4.4.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 13:17:04 +01:00
dependabot[bot]
62e01a8e4c
Bump coverage from 7.0.5 to 7.1.0 (#993)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 7.0.5 to 7.1.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/7.0.5...7.1.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-25 13:13:12 +01:00
Hasan Ramezani
a2a5507963 Run tests for Django main for Python >= 3.10 2023-01-21 18:40:20 +02:00
dependabot[bot]
15f2717a48
Bump tox from 4.3.4 to 4.3.5 (#991)
Bumps [tox](https://github.com/tox-dev/tox) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.3.4...4.3.5)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-19 13:18:41 +01:00
Hasan Ramezani
0c8180c42a Update dependancies 2023-01-19 08:37:02 +02:00
dependabot[bot]
dfd85002e1
Bump tox from 4.0.15 to 4.0.16 (#963)
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.15 to 4.0.16.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.15...4.0.16)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-21 13:37:57 +01:00
dependabot[bot]
7dfa18d9d4
Bump tox from 4.0.14 to 4.0.15 (#962)
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.14 to 4.0.15.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.14...4.0.15)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-20 13:29:34 +01:00
dependabot[bot]
1c06408753
Bump coverage from 6.5.0 to 7.0.0 (#961)
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.5.0 to 7.0.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.5.0...7.0.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 13:13:11 +01:00
dependabot[bot]
7614f1595c
Bump tox from 4.0.11 to 4.0.14 (#960)
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.11 to 4.0.14.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.11...4.0.14)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-19 13:12:24 +01:00
dependabot[bot]
e71d8ef20b Bump tox from 4.0.9 to 4.0.11
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.9 to 4.0.11.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.9...4.0.11)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-15 16:10:13 +03:30
dependabot[bot]
e455302244 Bump tox from 4.0.8 to 4.0.9
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.8 to 4.0.9.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.8...4.0.9)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-14 17:39:41 +02:00
dependabot[bot]
fce4642c1a Bump black from 22.10.0 to 22.12.0
Bumps [black](https://github.com/psf/black) from 22.10.0 to 22.12.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.10.0...22.12.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 16:16:39 +02:00
dependabot[bot]
a1a855815e Bump tox from 4.0.3 to 4.0.8
Bumps [tox](https://github.com/tox-dev/tox) from 4.0.3 to 4.0.8.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/4.0.3...4.0.8)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-12 16:16:29 +02:00
dependabot[bot]
673124e5e0 Bump tox from 3.27.1 to 4.0.3
Bumps [tox](https://github.com/tox-dev/tox) from 3.27.1 to 4.0.3.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/3.27.1...4.0.3)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-09 19:54:51 +02:00
dependabot[bot]
9e3729e97f Bump prospector from 1.8.2 to 1.8.3
Bumps [prospector](https://github.com/PyCQA/prospector) from 1.8.2 to 1.8.3.
- [Release notes](https://github.com/PyCQA/prospector/releases)
- [Changelog](https://github.com/PyCQA/prospector/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/prospector/compare/v1.8.2...v1.8.3)

---
updated-dependencies:
- dependency-name: prospector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-09 19:54:35 +02:00
Hasan Ramezani
c8f831bb62 Update prospector to 1.8.2 2022-12-03 14:12:51 +02:00
Aleksi Häkli
b65482ec98
Version 5.40.1 2022-11-24 22:01:20 +02:00
Petr Dlouhý
ce3f2f2c3f fix get_client_parameters when AXES_USE_USER_AGENT=True 2022-11-24 21:58:57 +02:00
Petr Dlouhý
f2d3009279 add user_agent test, fix get_client_parameters tests 2022-11-24 21:58:57 +02:00
Aleksi Häkli
a39ba7474b Version 5.40.0 2022-11-19 13:03:39 +02:00
dependabot[bot]
00a418a659 Bump mypy from 0.990 to 0.991
Bumps [mypy](https://github.com/python/mypy) from 0.990 to 0.991.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.990...v0.991)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 10:52:13 +02:00
Hasan Ramezani
2b24167dec Fix black formatting 2022-11-19 10:51:42 +02:00
liampauling
1825f9be4f add missing db alias to migration 0007
closes #932
2022-11-14 18:27:51 +02:00
JensDiemer
ca42b0f7dc Bugfix #921 cannot import name 'get_distribution'
Replace `pkg_resources` with `importlib` solution to fix
https://github.com/jazzband/django-axes/issues/921

Because `importlib.metadata` is new in Python 3.8, fallback to old `setuptools` solution.
2022-11-14 18:25:06 +02:00
Hasan Ramezani
5c7bea2a42 Update mypy to 0.990 2022-11-14 18:22:04 +02:00
dependabot[bot]
c556675da4 Bump tox from 3.27.0 to 3.27.1
Bumps [tox](https://github.com/tox-dev/tox) from 3.27.0 to 3.27.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/3.27.0...3.27.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 15:54:48 +03:30
Ihor Sychevskyi
b565a70d51
update links (#941) 2022-11-09 00:18:07 +02:00
Josh Thomas
e953872da9
Merge pull request #936 from joshuadavidthomas/py311
Add Python 3.11 to CI, tox, and trove classifiers
2022-11-08 12:19:16 -06:00
Josh
26a435e76e code review updates 2022-11-08 09:32:14 -06:00
dependabot[bot]
670de1efee Bump sphinx-rtd-theme from 1.1.0 to 1.1.1
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases)
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.1.0...1.1.1)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 15:45:34 +03:30
dependabot[bot]
8dd5823fae Bump pytest from 7.1.3 to 7.2.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.3 to 7.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.3...7.2.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-02 15:59:23 +03:30
dependabot[bot]
6f359b930f Bump sphinx-rtd-theme from 1.0.0 to 1.1.0
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases)
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.0.0...1.1.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-02 15:58:45 +03:30
dependabot[bot]
c01bad0b91 Bump pytest-subtests from 0.8.0 to 0.9.0
Bumps [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/pytest-dev/pytest-subtests/releases)
- [Changelog](https://github.com/pytest-dev/pytest-subtests/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-subtests/compare/0.8.0...0.9.0)

---
updated-dependencies:
- dependency-name: pytest-subtests
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-31 15:58:30 +03:30
Josh
2cf8d0afae add python 3.11 to CI, tox, and trove classifiers 2022-10-27 13:24:52 -05:00
dependabot[bot]
2978e68696 Bump tox from 3.26.0 to 3.27.0
Bumps [tox](https://github.com/tox-dev/tox) from 3.26.0 to 3.27.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/3.26.0...3.27.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-26 16:05:20 +03:30
dependabot[bot]
98ed2a28f3 Bump black from 22.8.0 to 22.10.0
Bumps [black](https://github.com/psf/black) from 22.8.0 to 22.10.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.8.0...22.10.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-07 14:25:27 +02:00
dependabot[bot]
113a45cef8 Bump mypy from 0.981 to 0.982
Bumps [mypy](https://github.com/python/mypy) from 0.981 to 0.982.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.981...v0.982)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-04 15:45:48 +02:00
dependabot[bot]
dea52c25bc Bump coverage from 6.4.4 to 6.5.0
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4.4 to 6.5.0.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4.4...6.5.0)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-30 14:20:29 +02:00
dependabot[bot]
1e474dc9cb Bump pytest-cov from 3.0.0 to 4.0.0
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-29 17:38:33 +02:00
Hasan Ramezani
2554a8cffd Update remaining part of Python 3.6 to 3.7 2022-09-28 11:50:26 +03:00
Nishit Mohanan
3ccc5a2843 Changed tox configuration link to pyproject.toml 2022-09-27 14:57:09 +02:00
dependabot[bot]
ea93e5e668 Bump mypy from 0.971 to 0.981
Bumps [mypy](https://github.com/python/mypy) from 0.971 to 0.981.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.971...v0.981)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-27 14:26:13 +02:00
dependabot[bot]
6a16fb5419 Bump tox from 3.25.1 to 3.26.0
Bumps [tox](https://github.com/tox-dev/tox) from 3.25.1 to 3.26.0.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/3.25.1...3.26.0)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-08 14:23:35 +02:00
dependabot[bot]
293199b1d4 Bump pytest from 7.1.2 to 7.1.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.2...7.1.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-02 14:16:57 +02:00
dependabot[bot]
ad4354cb97 Bump black from 22.6.0 to 22.8.0
Bumps [black](https://github.com/psf/black) from 22.6.0 to 22.8.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.6.0...22.8.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-01 14:30:17 +02:00
Tim Gates
ddc8cb7cb2 docs: Fix a few typos
There are small typos in:
- docs/5_customization.rst
- docs/7_architecture.rst

Fixes:
- Should read `necessary` rather than `nessary`.
- Should read `exception` rather than `excepton`.

Signed-off-by: Tim Gates <tim.gates@iress.com>
2022-09-01 12:55:33 +02:00
Aleksi Häkli
3c352566ab
Version 5.39.0 2022-08-18 16:33:17 +03:00
Simon Kern
556b56a84b update testconfig: use AxesStandaloneBackend 2022-08-18 16:32:00 +03:00
Simon Kern
5590419f81 Use new backend base class in checks
In 5c4bca6cb6 a new backend  base class was introduced. However the check and its corresponding tests still reference the old base class, thus triggering a warning on setups using the new backend base class.

resolves #907
2022-08-18 16:32:00 +03:00
dependabot[bot]
d368bc364b Bump coverage from 6.4.3 to 6.4.4
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4.3 to 6.4.4.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4.3...6.4.4)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-17 14:26:12 +02:00
Aleksi Häkli
7c0e53a95b
Version 5.38.0 2022-08-16 15:25:26 +03:00
Aleksi Häkli
ea3a551b64
Version 5.38.0 2022-08-16 15:24:42 +03:00
Aleksi Häkli
a03e81328f
Allow PyPy 3.8 test to fail for Django 4.1
Further information on

https://foss.heptapod.net/pypy/pypy/-/issues/3751
2022-08-16 15:12:23 +03:00
Aleksi Häkli
b4c68154d0
Allow PyPy 3.8 test to fail for Django 4.1
Further information on

https://foss.heptapod.net/pypy/pypy/-/issues/3751
2022-08-16 15:10:44 +03:00
Hasan Ramezani
ddb618c276 Confirm Django 4.1 support 2022-08-16 15:09:00 +03:00
dependabot[bot]
d9c4a43691 Bump coverage from 6.4.2 to 6.4.3
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4.2 to 6.4.3.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4.2...6.4.3)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 14:25:51 +02:00
dependabot[bot]
37bffab76c Bump mypy from 0.961 to 0.971
Bumps [mypy](https://github.com/python/mypy) from 0.961 to 0.971.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.961...v0.971)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-20 14:28:47 +02:00
Aleksi Häkli
979699b545
Version 5.36.0 2022-07-17 17:22:13 +03:00
Aleksi Häkli
e168a9126b
Fix code formatting 2022-07-17 17:19:05 +03:00
Jonathan Giuffrida
e8293df2ad Update documentation 2022-07-17 17:12:57 +03:00
Jonathan Giuffrida
5c4bca6cb6 Add AxesStandaloneBackend 2022-07-17 17:12:57 +03:00
dependabot[bot]
ca32979ae7 Bump coverage from 6.4.1 to 6.4.2
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4.1...6.4.2)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-13 14:24:55 +02:00
dependabot[bot]
6013b1e9fb Bump tox from 3.25.0 to 3.25.1
Bumps [tox](https://github.com/tox-dev/tox) from 3.25.0 to 3.25.1.
- [Release notes](https://github.com/tox-dev/tox/releases)
- [Changelog](https://github.com/tox-dev/tox/blob/master/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/tox/compare/3.25.0...3.25.1)

---
updated-dependencies:
- dependency-name: tox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-30 14:17:51 +02:00
dependabot[bot]
8f305155bf Bump black from 22.3.0 to 22.6.0
Bumps [black](https://github.com/psf/black) from 22.3.0 to 22.6.0.
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/22.3.0...22.6.0)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-28 17:18:12 +02:00
dependabot[bot]
408f0e6ace Bump actions/setup-python from 3 to 4
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-14 21:40:27 +03:00
dependabot[bot]
4e33d127ce Bump mypy from 0.960 to 0.961
Bumps [mypy](https://github.com/python/mypy) from 0.960 to 0.961.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.960...v0.961)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-07 14:19:44 +02:00
Ronny Vedrilla
57ca4460b0 #897: Bugfix with wrong display of default value in docs 2022-06-04 00:31:20 +03:00
Ronny Vedrilla
5e204be6c0 #897: Settings variables as table instead of list in the docs 2022-06-03 15:30:37 +03:00
dependabot[bot]
92355b06b6 Bump coverage from 6.4 to 6.4.1
Bumps [coverage](https://github.com/nedbat/coveragepy) from 6.4 to 6.4.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/6.4...6.4.1)

---
updated-dependencies:
- dependency-name: coverage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-03 15:21:08 +03:00
Aleksi Häkli
3edb33c5c8
Version 5.35.0 2022-06-01 21:14:17 +03:00
Yousef Almutairi
3448cba6b1 Added Arabic translations 2022-06-01 20:59:25 +03:00
79 changed files with 3070 additions and 712 deletions

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve django-axes
title: 'BUG: Short description of the problem'
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
4.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Your environment**
python version:
django version:
django-axes version:
Operating system:
**Additional context**
Add any other context about the problem here.
**Possible implementation**
Not obligatory, but suggest an idea for implementing addition or change

View file

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for django-axes
title: 'FEATURE REQUEST: Short description of requested feature'
labels: 'feature request'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

19
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,19 @@
# What does this PR do?
<!--
Congratulations! You've made it this far! You're not quite done yet though.
Please replace this with a description of the change and which issue is fixed (if applicable). Please also include relevant motivation and context. List any dependencies (if any) that are required for this change.
Once you're done, someone will review your PR shortly. They may suggest changes to make the code even better.
-->
<!-- Remove if not applicable -->
Fixes # (issue)
## Before submitting
- [ ] This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
- [ ] Did you make sure to update the documentation with your changes?
- [ ] Did you write any new necessary tests?

View file

@ -14,11 +14,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v4
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@ -26,7 +26,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below).
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v4
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@ -40,4 +40,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v4

View file

@ -14,14 +14,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: 3.8
python-version: 3.12
- name: Install dependencies
run: |
@ -36,8 +36,8 @@ jobs:
- name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-axes/upload
repository-url: https://jazzband.co/projects/django-axes/upload

View file

@ -11,39 +11,32 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8']
django-version: ['3.2', '4.0']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
django-version: ['4.2', '5.2', '6.0']
include:
# Tox configuration for QA environment
- python-version: '3.10'
- python-version: '3.14'
django-version: 'qa'
# Django main
- python-version: '3.8'
django-version: 'main'
experimental: true
- python-version: '3.9'
django-version: 'main'
experimental: true
- python-version: '3.10'
django-version: 'main'
experimental: true
- python-version: 'pypy-3.8'
- python-version: '3.14'
django-version: 'main'
experimental: true
exclude:
# Exclude Python 3.7 for Django 4.0 and Django main
- python-version: '3.7'
django-version: '4.0'
- python-version: '3.7'
django-version: 'main'
- python-version: '3.13'
django-version: '4.2'
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.10'
django-version: '6.0'
- python-version: '3.11'
django-version: '6.0'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
@ -53,7 +46,7 @@ jobs:
echo "::set-output name=dir::$(pip cache dir)"
- name: Cache
uses: actions/cache@v3
uses: actions/cache@v5
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:

9
.gitignore vendored
View file

@ -16,4 +16,11 @@ docs/_build
test.db
.eggs
pip-wheel-metadata
.vscode/
.vscode/
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

View file

@ -5,3 +5,11 @@ ignore-paths:
pycodestyle:
options:
max-line-length: 142
pylint:
disable:
- django-not-configured
pyflakes:
disable:
- F401

15
.readthedocs.yaml Normal file
View file

@ -0,0 +1,15 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
formats:
- pdf
- epub
python:
install:
- requirements: requirements.txt

View file

@ -2,6 +2,297 @@
Changes
=======
8.3.1 (2026-02-11)
------------------
- Fix configuration JSON serialization errors for Celery.
[aleksihakli]
8.3.0 (2026-02-09)
------------------
- Remove deprecated pkg_resources in favour of new importlib.
[hugovk]
8.2.0 (2026-02-06)
------------------
- Fix AttributeError when optional settings are undefined.
[rodrigo.nogueira]
- Fix circular import with custom user models.
[rodrigo.nogueira]
- Add unit tests for security check W006.
[shayanTaki]
8.1.0 (2025-12-19)
------------------
- Add Persion (fa) translations for django-axes.
[AmirAli-BahramJerdi]
- Add individual attempt expiry support.
[kuldeepkhatke]
- Add checks for missing ip_address in lockout params.
[shayanTaki]
- Add missing ``settings.AXES_IPWARE_PROXY_ORDER`` documentation.
[ram98kgp]
- Enhance ``get_lockout_response`` to receive original response as parameter.
[mounirmesselmeni]
- Update documentation.
- Add Python 3.14 support.
- Add Django 6.0 support.
- Remove Python 3.9 support (EOL).
- Remove Django 5.1 support (EOL).
8.0.0 (2025-05-10)
------------------
- Move all database related logic to the default ``axes.handlers.database.AxesDatabaseHandler``.
[nefrob]
7.1.0 (2025-04-23)
------------------
- Provide credentials to expired credentials cleanup method.
[parul-aro]
- Update support matrix for Django 5.2.
[mkniewallner]
- Fix documentation.
[chango-goat]
7.0.2 (2025-02-19)
------------------
- Fix documentation.
[Jacobus-afk]
- Default to using ``settings.AUTH_USER_MODEL.USERNAME_FIELD`` for resolving ``settings.AXES_USERNAME_FORM_FIELD`` if otherwise unset (previously "username").
[amneher]
7.0.1 (2024-12-02)
------------------
- Add Python 3.13 support.
[aleksihakli]
- Deprecate Python 3.8 support.
[aleksihakli]
7.0.0 (2024-10-02)
------------------
- Add support for dynamic cooloff time calculation from request. This is a breaking change. Please see `version 7 upgrade notes in the documentation <https://github.com/jazzband/django-axes/blob/4e89d72b92db044ff3f6b23ea2ab2e681211c98e/docs/2_installation.rst#version-7-breaking-changes-and-upgrading-from-django-axes-version-6>`_.
[browniebroke]
6.5.2 (2024-09-21)
------------------
- Add test matrix support for Django 5.1.
- Drop support for EOL Django 3.2.
- Drop support for PyPy 3.10.
6.5.1 (2024-07-01)
------------------
- Make 0007_alter_accessattempt_unique_together.py migration backwards compatible.
[hirotasoshu]
6.5.0 (2024-06-11)
------------------
- Add session hash to access log.
[sevdog]
6.4.0 (2024-03-04)
------------------
- Add support for Python 3.12 and Django 5.0, drop support for Django 4.1.
[aleksihakli]
6.3.1 (2024-03-04)
------------------
- Drop ``setuptools`` and ``pkg_resources`` dependencies.
[Viicos]
6.3.0 (2023-12-27)
------------------
- Add async support to middleware.
[Taikono-Himazin]
6.2.0 (2023-12-08)
------------------
- Update documentation.
[funkybob]
- Add new management command ``axes_reset_ip_username``.
[p-l-]
- Add French translations.
[laulaz]
- Avoid running data migration on incorrect databases.
[christianbundy]
6.1.1 (2023-08-01)
------------------
- Fix ``TransactionManagementError`` when using the database handler
with a custom database with for ``AccessAttempt`` or ``AccessFailureLog``.
[hirotasoshu]
6.1.0 (2023-07-30)
------------------
- Set ``AXES_SENSITIVE_PARAMETERS`` default value to ``["username", "ip_address"]`` in addition to the ``AXES_PASSWORD_FORM_FIELD`` configuration flag.
This masks the username and IP address fields by default in the logs when writing information about login attempts to the application logs.
Reverting to old configuration default of ``[]`` can be done by setting ``AXES_SENSITIVE_PARAMETERS = []`` in the Django project settings file.
[GitRon]
- Improve documentation on GDPR and privacy notes and configuration flags.
[GitRon]
6.0.5 (2023-07-01)
------------------
- Add Indonesion translation.
[kiraware]
6.0.4 (2023-06-22)
------------------
- Remove unused methods from AxesStandaloneBackend.
[314eter]
6.0.3 (2023-06-18)
------------------
- Add username to admin fieldsets.
[sevdog]
6.0.2 (2023-06-13)
------------------
- Add Django system checks for validating callable import path settings.
[iafisher]
- Improve documentation.
[hirotasoshu]
- Improve repository issue and PR templates.
[hirotasoshu]
6.0.1 (2023-05-17)
------------------
- Fine-tune CI pipelines and RTD build requirements.
[aleksihakli]
6.0.0 (2023-05-17)
------------------
Version 6 is a breaking release. Please see the documentation for upgrade instructions.
- Deprecate Python 3.7 support.
[aleksihakli]
- Deprecate ``is_admin_site`` API call with misleading naming.
[hirotasoshu]
- Add ``AXES_LOCKOUT_PARAMETERS`` configuration flag that will supersede ``AXES_ONLY_USER_FAILURES``, ``AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``, ``AXES_LOCK_OUT_BY_USER_OR_IP``, and ``AXES_USE_USER_AGENT`` configurations. Add deprecation warnings for old flags. See project documentation on RTD for update instructions.
[hirotasoshu]
- Improve translations.
[hirotasoshu]
- Use Django ``cache.incr`` API for atomic cached failure counting
[hirotasoshu, aleksihakli]
- Make ``django-ipware`` an optional dependency. Install it with e.g. ``pip install django-axes[ipware]`` package and extras specifier. [aleksihakli]
- Deprecate and rename old configuration flags. Old flags will be removed in or after version ``6.1``. [aleksihakli]
* ``AXES_PROXY_ORDER`` is now ``AXES_IPWARE_PROXY_ORDER``,
* ``AXES_PROXY_COUNT`` is now ``AXES_IPWARE_PROXY_COUNT``,
* ``AXES_PROXY_TRUSTED_IPS`` is now ``AXES_IPWARE_PROXY_TRUSTED_IPS``, and
* ``AXES_META_PRECEDENCE_ORDER`` is now ``AXES_IPWARE_META_PRECEDENCE_ORDER``.
- Set 429 as the default lockout response code. [hirotasoshu]
5.41.1 (2023-04-16)
-------------------
- Fix sensitive parameter logging for database handler. [stereodamage]
5.41.0 (2023-04-02)
-------------------
- Fix tests. [hirotasoshu]
- Add ``AXES_CLIENT_CALLABLE`` setting. [hirotasoshu]
- Update Python, Django, and package versions. [hramezani]
5.40.1 (2022-11-24)
-------------------
- Fix bug in user agent request blocking. [PetrDlouhy]
5.40.0 (2022-11-19)
-------------------
- Update packages and linters for new version support.
[hramezani]
- Update documentation links.
[Arhell]
- Use importlib instead of setuptools for Python 3.8+.
[jedie]
- Python 3.11 support.
[joshuadavidthomas]
- Documentation improvements.
[nsht]
- Documentation improvements.
[timgates42]
5.39.0 (2022-08-18)
-------------------
- Utilize new backend class in tests to fix false negative system check warnings.
[simonkern]
5.38.0 (2022-08-16)
-------------------
- Adjust changelog so release notes are correctly visible on PyPy and released package.
[aleksihakli]
5.37.0 (2022-08-16)
-------------------
- Add Django 4.1 support. PyPy 3.8 has a known issue with Django 4.1 and is exempted.
[hramezani]
5.36.0 (2022-07-17)
-------------------
- Add ``AxesStandaloneBackend`` without ``ModelBackend`` dependencies.
[jcgiuffrida]
5.35.0 (2022-06-01)
-------------------
- Add Arabic translations.
[YDA93]
5.34.0 (2022-05-28)
-------------------

View file

@ -1,4 +1,30 @@
.. _development:
.. image:: https://jazzband.co/static/img/jazzband.svg
:target: https://jazzband.co/
:alt: Jazzband
This is a `Jazzband <https://jazzband.co>`_ project. By contributing you agree to abide by the `Contributor Code of Conduct <https://jazzband.co/about/conduct>`_ and follow the `guidelines <https://jazzband.co/about/guidelines>`_.
Contributions
=============
All contributions are welcome!
It is best to separate proposed changes and PRs into small, distinct patches
by type so that they can be merged faster into upstream and released quicker.
One way to organize contributions would be to separate PRs for e.g.
* bugfixes,
* new features,
* code and design improvements,
* documentation improvements, or
* tooling and CI improvements.
Merging contributions requires passing the checks configured
with the CI. This includes running tests and linters successfully
on the currently officially supported Python and Django versions.
Development
===========

View file

@ -57,8 +57,8 @@ or alternatively use a fast and DDoS resistant cache implementation.
Axes can be configured to monitor login attempts by
IP address, username, user agent, or their combinations.
Axes supports cool off periods, IP address whitelisting and blacklisting,
user account whitelisting, and other features for Django access management.
Axes supports cool off periods, IP address allow listing and block listing,
user account allow listing, and other features for Django access management.
Documentation
@ -77,30 +77,7 @@ If you have questions or have trouble using the app please file a bug report at:
https://github.com/jazzband/django-axes/issues
Contributions
-------------
Contributing
------------
All contributions are welcome!
It is best to separate proposed changes and PRs into small, distinct patches
by type so that they can be merged faster into upstream and released quicker.
One way to organize contributions would be to separate PRs for e.g.
* bugfixes,
* new features,
* code and design improvements,
* documentation improvements, or
* tooling and CI improvements.
Merging contributions requires passing the checks configured
with the CI. This includes running tests and linters successfully
on the currently officially supported Python and Django versions.
The test automation is run automatically with GitHub Actions, but you can
run it locally with the ``tox`` command before pushing commits.
Please note that this is a `Jazzband <https://jazzband.co>`_ project.
By contributing you agree to abide by the
`Contributor Code of Conduct <https://jazzband.co/about/conduct>`_
and follow the `guidelines <https://jazzband.co/about/guidelines>`_.
See `CONTRIBUTING <CONTRIBUTING.rst>`__.

View file

@ -1,4 +1,3 @@
from pkg_resources import get_distribution
from importlib.metadata import version
__version__ = get_distribution("django-axes").version
__version__ = version("django-axes")

View file

@ -4,26 +4,59 @@ from django.utils.translation import gettext_lazy as _
from axes.conf import settings
from axes.models import AccessAttempt, AccessLog, AccessFailureLog
from axes.handlers.database import AxesDatabaseHandler
class IsLockedOutFilter(admin.SimpleListFilter):
title = _("Locked Out")
parameter_name = "locked_out"
def lookups(self, request, model_admin):
return (
("yes", _("Yes")),
("no", _("No")),
)
def queryset(self, request, queryset):
if self.value() == "yes":
return queryset.filter(
failures_since_start__gte=settings.AXES_FAILURE_LIMIT
)
if self.value() == "no":
return queryset.filter(failures_since_start__lt=settings.AXES_FAILURE_LIMIT)
return queryset
class AccessAttemptAdmin(admin.ModelAdmin):
list_display = (
list_display = [
"attempt_time",
"ip_address",
"user_agent",
"username",
"path_info",
"failures_since_start",
)
]
if settings.AXES_USE_ATTEMPT_EXPIRATION:
list_display.append("expiration")
list_filter = ["attempt_time", "path_info"]
if isinstance(settings.AXES_FAILURE_LIMIT, int) and settings.AXES_FAILURE_LIMIT > 0:
# This will only add the status field if AXES_FAILURE_LIMIT is set to a positive integer
# Because callable failure limit requires scope of request object
list_display.append("status")
list_filter.append(IsLockedOutFilter) # type: ignore[arg-type]
search_fields = ["ip_address", "username", "user_agent", "path_info"]
date_hierarchy = "attempt_time"
fieldsets = (
(None, {"fields": ("path_info", "failures_since_start")}),
(
None,
{"fields": ("username", "path_info", "failures_since_start", "expiration")},
),
(_("Form Data"), {"fields": ("get_data", "post_data")}),
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
)
@ -38,11 +71,34 @@ class AccessAttemptAdmin(admin.ModelAdmin):
"get_data",
"post_data",
"failures_since_start",
"expiration",
]
actions = ["cleanup_expired_attempts"]
@admin.action(description=_("Clean up expired attempts"))
def cleanup_expired_attempts(self, request, queryset): # noqa
count = self.handler.clean_expired_user_attempts(request=request)
self.message_user(request, _(f"Cleaned up {count} expired access attempts."))
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.handler = AxesDatabaseHandler()
def has_add_permission(self, request: HttpRequest) -> bool:
return False
def expiration(self, obj: AccessAttempt):
return obj.expiration.expires_at if hasattr(obj, "expiration") else _("Not set")
def status(self, obj: AccessAttempt):
return (
f"{settings.AXES_FAILURE_LIMIT - obj.failures_since_start} "
+ _("Attempt Remaining")
if obj.failures_since_start < settings.AXES_FAILURE_LIMIT
else _("Locked Out")
)
class AccessLogAdmin(admin.ModelAdmin):
list_display = (
@ -61,7 +117,7 @@ class AccessLogAdmin(admin.ModelAdmin):
date_hierarchy = "attempt_time"
fieldsets = (
(None, {"fields": ("path_info",)}),
(None, {"fields": ("username", "path_info")}),
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
)
@ -96,7 +152,7 @@ class AccessFailureLogAdmin(admin.ModelAdmin):
date_hierarchy = "attempt_time"
fieldsets = (
(None, {"fields": ("path_info",)}),
(None, {"fields": ("username", "path_info")}),
(_("Meta Data"), {"fields": ("user_agent", "ip_address", "http_accept")}),
)

View file

@ -1,7 +1,10 @@
# pylint: disable=import-outside-toplevel, unused-import
from logging import getLogger
from django import apps
from pkg_resources import get_distribution
from axes import __version__
log = getLogger(__name__)
@ -25,22 +28,31 @@ class AppConfig(apps.AppConfig):
cls.initialized = True
# Only import settings, checks, and signals one time after Django has been initialized
from axes.conf import settings # noqa
from axes import checks, signals # noqa
from axes.conf import settings
from axes import checks, signals
# Skip startup log messages if Axes is not set to verbose
if settings.AXES_VERBOSE:
if settings.AXES_ONLY_USER_FAILURES:
mode = "blocking by username only"
elif settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP:
mode = "blocking by combination of username and IP"
elif settings.AXES_LOCK_OUT_BY_USER_OR_IP:
mode = "blocking by username or IP"
if callable(settings.AXES_LOCKOUT_PARAMETERS) or isinstance(
settings.AXES_LOCKOUT_PARAMETERS, str
):
mode = "blocking by parameters that are calculated in a custom callable"
else:
mode = "blocking by IP only"
mode = "blocking by " + " or ".join(
[
(
param
if isinstance(param, str)
else "combination of " + " and ".join(param)
)
for param in settings.AXES_LOCKOUT_PARAMETERS
]
)
log.info(
"AXES: BEGIN version %s, %s",
get_distribution("django-axes").version,
__version__,
mode,
)

View file

@ -1,106 +1,26 @@
from logging import getLogger
from typing import List, Optional
from typing import Optional
from django.db.models import QuerySet
from django.http import HttpRequest
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
from axes.helpers import get_cool_off
log = getLogger(__name__)
def get_cool_off_threshold(attempt_time: Optional[datetime] = None) -> datetime:
def get_cool_off_threshold(request: Optional[HttpRequest] = None) -> datetime:
"""
Get threshold for fetching access attempts from the database.
"""
cool_off = get_cool_off()
cool_off = get_cool_off(request)
if cool_off is None:
raise TypeError(
"Cool off threshold can not be calculated with settings.AXES_COOLOFF_TIME set to None"
)
attempt_time = request.axes_attempt_time # type: ignore[union-attr]
if attempt_time is None:
return now() - cool_off
return attempt_time - cool_off
def filter_user_attempts(
request: HttpRequest, credentials: Optional[dict] = None
) -> List[QuerySet]:
"""
Return a list querysets of AccessAttempts that match the given request and credentials.
"""
username = get_client_username(request, credentials)
filter_kwargs_list = get_client_parameters(
username, request.axes_ip_address, request.axes_user_agent
)
attempts_list = [
AccessAttempt.objects.filter(**filter_kwargs)
for filter_kwargs in filter_kwargs_list
]
return attempts_list
def get_user_attempts(
request: HttpRequest, credentials: Optional[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: Optional[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()
log.info(
"AXES: Cleaned up %s expired access attempts from database that were older than %s",
count,
threshold,
)
return count
def reset_user_attempts(
request: HttpRequest, credentials: Optional[dict] = None
) -> int:
"""
Reset all user attempts that match the given request and credentials.
"""
attempts_list = filter_user_attempts(request, credentials)
count = 0
for attempts in attempts_list:
_count, _ = attempts.delete()
count += _count
log.info("AXES: Reset %s access attempts from database.", count)
return count

View file

@ -11,7 +11,7 @@ from axes.handlers.proxy import AxesProxyHandler
from axes.helpers import get_credentials, get_lockout_message, toggleable
class AxesBackend(ModelBackend):
class AxesStandaloneBackend:
"""
Authentication backend class that forbids login attempts for locked out users.
@ -19,6 +19,7 @@ class AxesBackend(ModelBackend):
prevent locked out users from being logged in by the Django authentication flow.
.. note:: This backend does not log your user in. It monitors login attempts.
It also does not run any permissions checks at all.
Authentication is handled by the following backends that are configured in ``AUTHENTICATION_BACKENDS``.
"""
@ -71,3 +72,16 @@ class AxesBackend(ModelBackend):
raise AxesBackendPermissionDenied(
"AxesBackend detected that the given user is locked out"
)
class AxesBackend(AxesStandaloneBackend, ModelBackend):
"""
Axes authentication backend that also inherits from ModelBackend,
and thus also performs other functions of ModelBackend such as permissions checks.
Use this class as the first item of ``AUTHENTICATION_BACKENDS`` to
prevent locked out users from being logged in by the Django authentication flow.
.. note:: This backend does not log your user in. It monitors login attempts.
Authentication is handled by the following backends that are configured in ``AUTHENTICATION_BACKENDS``.
"""

View file

@ -5,7 +5,7 @@ from django.core.checks import ( # pylint: disable=redefined-builtin
)
from django.utils.module_loading import import_string
from axes.backends import AxesBackend
from axes.backends import AxesStandaloneBackend
from axes.conf import settings
@ -19,17 +19,22 @@ class Messages:
MIDDLEWARE_INVALID = (
"You do not have 'axes.middleware.AxesMiddleware' in your settings.MIDDLEWARE."
)
BACKEND_INVALID = "You do not have 'axes.backends.AxesBackend' or a subclass in your settings.AUTHENTICATION_BACKENDS."
BACKEND_INVALID = "You do not have 'axes.backends.AxesStandaloneBackend' or a subclass in your settings.AUTHENTICATION_BACKENDS."
SETTING_DEPRECATED = "You have a deprecated setting {deprecated_setting} configured in your project settings"
CALLABLE_INVALID = "{callable_setting} is not a valid callable."
LOCKOUT_PARAMETERS_INVALID = (
"AXES_LOCKOUT_PARAMETERS does not contain 'ip_address'."
" This configuration allows attackers to bypass rate limits by rotating User-Agents or Cookies."
)
class Hints:
CACHE_INVALID = None
MIDDLEWARE_INVALID = None
BACKEND_INVALID = (
"AxesModelBackend was renamed to AxesBackend in django-axes version 5.0."
)
BACKEND_INVALID = "AxesModelBackend was renamed to AxesStandaloneBackend in django-axes version 5.0."
SETTING_DEPRECATED = None
CALLABLE_INVALID = None
LOCKOUT_PARAMETERS_INVALID = "Add 'ip_address' to AXES_LOCKOUT_PARAMETERS."
class Codes:
@ -37,6 +42,8 @@ class Codes:
MIDDLEWARE_INVALID = "axes.W002"
BACKEND_INVALID = "axes.W003"
SETTING_DEPRECATED = "axes.W004"
CALLABLE_INVALID = "axes.W005"
LOCKOUT_PARAMETERS_INVALID = "axes.W006"
@register(Tags.security, Tags.caches, Tags.compatibility)
@ -101,7 +108,7 @@ def axes_backend_check(app_configs, **kwargs): # pylint: disable=unused-argumen
"Can not import backend class defined in settings.AUTHENTICATION_BACKENDS"
) from e
if issubclass(backend, AxesBackend):
if issubclass(backend, AxesStandaloneBackend):
found = True
break
@ -124,6 +131,19 @@ def axes_deprecation_check(app_configs, **kwargs): # pylint: disable=unused-arg
deprecated_settings = [
"AXES_DISABLE_SUCCESS_ACCESS_LOG",
"AXES_LOGGER",
# AXES_PROXY_ and AXES_META_ parameters were updated to more explicit
# AXES_IPWARE_PROXY_ and AXES_IPWARE_META_ prefixes in version 6.x
"AXES_PROXY_ORDER",
"AXES_PROXY_COUNT",
"AXES_PROXY_TRUSTED_IPS",
"AXES_META_PRECEDENCE_ORDER",
# AXES_ONLY_USER_FAILURES, AXES_USE_USER_AGENT and
# AXES_LOCK_OUT parameters were replaced with AXES_LOCKOUT_PARAMETERS
# in version 6.x
"AXES_ONLY_USER_FAILURES",
"AXES_LOCK_OUT_BY_USER_OR_IP",
"AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP",
"AXES_USE_USER_AGENT",
]
for deprecated_setting in deprecated_settings:
@ -142,3 +162,77 @@ def axes_deprecation_check(app_configs, **kwargs): # pylint: disable=unused-arg
pass
return warnings
@register(Tags.security)
def axes_lockout_params_check(app_configs, **kwargs): # pylint: disable=unused-argument
warnings = []
lockout_params = getattr(settings, "AXES_LOCKOUT_PARAMETERS", None)
if isinstance(lockout_params, (list, tuple)):
has_ip = False
for param in lockout_params:
if param == "ip_address":
has_ip = True
break
if isinstance(param, (list, tuple)) and "ip_address" in param:
has_ip = True
break
if not has_ip:
warnings.append(
Warning(
msg=Messages.LOCKOUT_PARAMETERS_INVALID,
hint=Hints.LOCKOUT_PARAMETERS_INVALID,
id=Codes.LOCKOUT_PARAMETERS_INVALID,
)
)
return warnings
@register
def axes_conf_check(app_configs, **kwargs): # pylint: disable=unused-argument
warnings = []
callable_settings = [
"AXES_CLIENT_IP_CALLABLE",
"AXES_CLIENT_STR_CALLABLE",
"AXES_LOCKOUT_CALLABLE",
"AXES_USERNAME_CALLABLE",
"AXES_WHITELIST_CALLABLE",
"AXES_COOLOFF_TIME",
"AXES_LOCKOUT_PARAMETERS",
]
for callable_setting in callable_settings:
value = getattr(settings, callable_setting, None)
if not is_valid_callable(value):
warnings.append(
Warning(
msg=Messages.CALLABLE_INVALID.format(
callable_setting=callable_setting
),
hint=Hints.CALLABLE_INVALID,
id=Codes.CALLABLE_INVALID,
)
)
return warnings
def is_valid_callable(value) -> bool:
if value is None:
return True
if callable(value):
return True
if isinstance(value, str):
try:
import_string(value)
except ImportError:
return False
return True

View file

@ -1,7 +1,21 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.functional import SimpleLazyObject
from django.utils.translation import gettext_lazy as _
class JSONSerializableLazyObject(SimpleLazyObject):
"""
Celery/Kombu config inspection may JSON-encode Django settings.
Provide a JSON-friendly representation for lazy values.
Fixes jazzband/django-axes#1391
"""
def __json__(self):
return str(self)
# disable plugin when set to False
settings.AXES_ENABLED = getattr(settings, "AXES_ENABLED", True)
@ -11,18 +25,30 @@ settings.AXES_FAILURE_LIMIT = getattr(settings, "AXES_FAILURE_LIMIT", 3)
# see if the user has set axes to lock out logins after failure limit
settings.AXES_LOCK_OUT_AT_FAILURE = getattr(settings, "AXES_LOCK_OUT_AT_FAILURE", True)
# lock out with the combination of username and IP address
settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP = getattr(
settings, "AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP", False
)
# lockout parameters
# default value will be ["ip_address"] after removing AXES_LOCK_OUT params support
settings.AXES_LOCKOUT_PARAMETERS = getattr(settings, "AXES_LOCKOUT_PARAMETERS", None)
# lock out with the username or IP address
settings.AXES_LOCK_OUT_BY_USER_OR_IP = getattr(
settings, "AXES_LOCK_OUT_BY_USER_OR_IP", False
)
# TODO: remove it in future versions
if settings.AXES_LOCKOUT_PARAMETERS is None:
if getattr(settings, "AXES_ONLY_USER_FAILURES", False):
settings.AXES_LOCKOUT_PARAMETERS = ["username"]
else:
if getattr(settings, "AXES_LOCK_OUT_BY_USER_OR_IP", False):
settings.AXES_LOCKOUT_PARAMETERS = ["username", "ip_address"]
elif getattr(settings, "AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP", False):
settings.AXES_LOCKOUT_PARAMETERS = [["username", "ip_address"]]
else:
settings.AXES_LOCKOUT_PARAMETERS = ["ip_address"]
# lock out with username and never the IP or user agent
settings.AXES_ONLY_USER_FAILURES = getattr(settings, "AXES_ONLY_USER_FAILURES", False)
if getattr(settings, "AXES_USE_USER_AGENT", False):
if isinstance(settings.AXES_LOCKOUT_PARAMETERS[0], str):
settings.AXES_LOCKOUT_PARAMETERS[0] = [
settings.AXES_LOCKOUT_PARAMETERS[0],
"user_agent",
]
else:
settings.AXES_LOCKOUT_PARAMETERS[0].append("user_agent")
# lock out just for admin site
settings.AXES_ONLY_ADMIN_SITE = getattr(settings, "AXES_ONLY_ADMIN_SITE", False)
@ -30,12 +56,16 @@ settings.AXES_ONLY_ADMIN_SITE = getattr(settings, "AXES_ONLY_ADMIN_SITE", False)
# show Axes logs in admin
settings.AXES_ENABLE_ADMIN = getattr(settings, "AXES_ENABLE_ADMIN", True)
# lock out with the user agent, has no effect when ONLY_USER_FAILURES is set
settings.AXES_USE_USER_AGENT = getattr(settings, "AXES_USE_USER_AGENT", False)
# use a specific username field to retrieve from login POST data
def _get_username_field_default():
return get_user_model().USERNAME_FIELD
settings.AXES_USERNAME_FORM_FIELD = getattr(
settings, "AXES_USERNAME_FORM_FIELD", "username"
settings,
"AXES_USERNAME_FORM_FIELD",
JSONSerializableLazyObject(_get_username_field_default),
)
# use a specific password field to retrieve from login POST data
@ -52,6 +82,9 @@ settings.AXES_WHITELIST_CALLABLE = getattr(settings, "AXES_WHITELIST_CALLABLE",
# return custom lockout response if configured
settings.AXES_LOCKOUT_CALLABLE = getattr(settings, "AXES_LOCKOUT_CALLABLE", None)
# use a provided callable to get client ip address
settings.AXES_CLIENT_IP_CALLABLE = getattr(settings, "AXES_CLIENT_IP_CALLABLE", None)
# reset the number of failed attempts after one successful attempt
settings.AXES_RESET_ON_SUCCESS = getattr(settings, "AXES_RESET_ON_SUCCESS", False)
@ -75,6 +108,10 @@ settings.AXES_LOCKOUT_URL = getattr(settings, "AXES_LOCKOUT_URL", None)
settings.AXES_COOLOFF_TIME = getattr(settings, "AXES_COOLOFF_TIME", None)
settings.AXES_USE_ATTEMPT_EXPIRATION = getattr(
settings, "AXES_USE_ATTEMPT_EXPIRATION", False
)
settings.AXES_VERBOSE = getattr(settings, "AXES_VERBOSE", settings.AXES_ENABLED)
# whitelist and blacklist
@ -106,24 +143,6 @@ settings.AXES_PERMALOCK_MESSAGE = getattr(
),
)
# if your deployment is using reverse proxies, set this value to 'left-most' or 'right-most' per your configuration
settings.AXES_PROXY_ORDER = getattr(settings, "AXES_PROXY_ORDER", "left-most")
# if your deployment is using reverse proxies, set this value to the number of proxies in front of Django
settings.AXES_PROXY_COUNT = getattr(settings, "AXES_PROXY_COUNT", None)
# if your deployment is using reverse proxies, set to your trusted proxy IP addresses prefixes if needed
settings.AXES_PROXY_TRUSTED_IPS = getattr(settings, "AXES_PROXY_TRUSTED_IPS", None)
# set to the names of request.META attributes that should be checked for the IP address of the client
# if your deployment is using reverse proxies, ensure that the header attributes are securely set by the proxy
# ensure that the client can not spoof the headers by setting them and sending them through the proxy
settings.AXES_META_PRECEDENCE_ORDER = getattr(
settings,
"AXES_META_PRECEDENCE_ORDER",
getattr(settings, "IPWARE_META_PRECEDENCE_ORDER", ("REMOTE_ADDR",)),
)
# set CORS allowed origins when calling authentication over ajax
settings.AXES_ALLOWED_CORS_ORIGINS = getattr(settings, "AXES_ALLOWED_CORS_ORIGINS", "*")
@ -131,7 +150,7 @@ settings.AXES_ALLOWED_CORS_ORIGINS = getattr(settings, "AXES_ALLOWED_CORS_ORIGIN
settings.AXES_SENSITIVE_PARAMETERS = getattr(
settings,
"AXES_SENSITIVE_PARAMETERS",
[],
["username", "ip_address"],
)
# set the callable for the readable string that can be used in
@ -139,9 +158,50 @@ settings.AXES_SENSITIVE_PARAMETERS = getattr(
settings.AXES_CLIENT_STR_CALLABLE = getattr(settings, "AXES_CLIENT_STR_CALLABLE", None)
# set the HTTP response code given by too many requests
settings.AXES_HTTP_RESPONSE_CODE = getattr(settings, "AXES_HTTP_RESPONSE_CODE", 403)
settings.AXES_HTTP_RESPONSE_CODE = getattr(settings, "AXES_HTTP_RESPONSE_CODE", 429)
# If True, a failed login attempt during lockout will reset the cool off period
settings.AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT = getattr(
settings, "AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT", True
)
###
# django-ipware settings for client IP address calculation and proxy detection
# there are old AXES_PROXY_ and AXES_META_ legacy keys present for backwards compatibility
# see https://github.com/un33k/django-ipware for further details
###
# if your deployment is using reverse proxies, set this value to 'left-most' or 'right-most' per your configuration
settings.AXES_IPWARE_PROXY_ORDER = getattr(
settings,
"AXES_IPWARE_PROXY_ORDER",
getattr(settings, "AXES_PROXY_ORDER", "left-most"),
)
# if your deployment is using reverse proxies, set this value to the number of proxies in front of Django
settings.AXES_IPWARE_PROXY_COUNT = getattr(
settings,
"AXES_IPWARE_PROXY_COUNT",
getattr(settings, "AXES_PROXY_COUNT", None),
)
# if your deployment is using reverse proxies, set to your trusted proxy IP addresses prefixes if needed
settings.AXES_IPWARE_PROXY_TRUSTED_IPS = getattr(
settings,
"AXES_IPWARE_PROXY_TRUSTED_IPS",
getattr(settings, "AXES_PROXY_TRUSTED_IPS", None),
)
# set to the names of request.META attributes that should be checked for the IP address of the client
# if your deployment is using reverse proxies, ensure that the header attributes are securely set by the proxy
# ensure that the client can not spoof the headers by setting them and sending them through the proxy
settings.AXES_IPWARE_META_PRECEDENCE_ORDER = getattr(
settings,
"AXES_IPWARE_META_PRECEDENCE_ORDER",
getattr(
settings,
"AXES_META_PRECEDENCE_ORDER",
getattr(settings, "IPWARE_META_PRECEDENCE_ORDER", ("REMOTE_ADDR",)),
),
)

View file

@ -1,6 +1,7 @@
import re
from abc import ABC, abstractmethod
from typing import Optional
from warnings import warn
from django.urls import reverse
from django.urls.exceptions import NoReverseMatch
@ -81,7 +82,7 @@ class AxesBaseHandler: # pylint: disable=unused-argument
and inspiration on some common checks and access restrictions before writing your own implementation.
"""
if self.is_admin_site(request):
if settings.AXES_ONLY_ADMIN_SITE and not self.is_admin_request(request):
return True
if self.is_blacklisted(request, credentials):
@ -134,10 +135,41 @@ class AxesBaseHandler: # pylint: disable=unused-argument
return False
def get_admin_url(self) -> Optional[str]:
"""
Returns admin url if exists, otherwise returns None
"""
try:
return reverse("admin:index")
except NoReverseMatch:
return None
def is_admin_request(self, request) -> bool:
"""
Checks that request located under admin site
"""
if hasattr(request, "path"):
admin_url = self.get_admin_url()
return (
admin_url is not None
and re.match(f"^{admin_url}", request.path) is not None
)
return False
def is_admin_site(self, request) -> bool:
"""
Checks if the request is for admin site.
Checks if the request is NOT for admin site
if `settings.AXES_ONLY_ADMIN_SITE` is True.
"""
warn(
(
"This method is deprecated and will be removed in future versions. "
"If you looking for method that checks if `request.path` located under "
"admin site, use `is_admin_request` instead."
),
DeprecationWarning,
)
if settings.AXES_ONLY_ADMIN_SITE and hasattr(request, "path"):
try:
admin_url = reverse("admin:index")

View file

@ -6,11 +6,12 @@ from axes.handlers.base import AxesBaseHandler, AbstractAxesHandler
from axes.helpers import (
get_cache,
get_cache_timeout,
get_client_cache_key,
get_client_cache_keys,
get_client_str,
get_client_username,
get_credentials,
get_failure_limit,
get_lockout_parameters,
)
from axes.models import AccessAttempt
from axes.signals import user_locked_out
@ -29,8 +30,8 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
def reset_attempts(
self,
*,
ip_address: str = None,
username: str = None,
ip_address: Optional[str] = None,
username: Optional[str] = None,
ip_or_username: bool = False,
) -> int:
cache_keys: list = []
@ -44,7 +45,7 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
)
cache_keys.extend(
get_client_cache_key(
get_client_cache_keys(
AccessAttempt(username=username, ip_address=ip_address)
)
)
@ -58,7 +59,7 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
return count
def get_failures(self, request, credentials: Optional[dict] = None) -> int:
cache_keys = get_client_cache_key(request, credentials)
cache_keys = get_client_cache_keys(request, credentials)
failure_count = max(
self.cache.get(cache_key, default=0) for cache_key in cache_keys
)
@ -78,9 +79,10 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
return
username = get_client_username(request, credentials)
if settings.AXES_ONLY_USER_FAILURES and username is None:
lockout_parameters = get_lockout_parameters(request, credentials)
if lockout_parameters == ["username"] and username is None:
log.warning(
"AXES: Username is None and AXES_ONLY_USER_FAILURES is enabled, new record will NOT be created."
"AXES: Username is None and username is the only one lockout parameter, new record will NOT be created."
)
return
@ -110,7 +112,18 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
log.info("AXES: Login failed from whitelisted client %s.", client_str)
return
failures_since_start = 1 + self.get_failures(request, credentials)
cache_keys = get_client_cache_keys(request, credentials)
cache_timeout = get_cache_timeout(request)
failures = []
for cache_key in cache_keys:
added = self.cache.add(key=cache_key, value=1, timeout=cache_timeout)
if added:
failures.append(1)
else:
failures.append(self.cache.incr(key=cache_key, delta=1))
self.cache.touch(key=cache_key, timeout=cache_timeout)
failures_since_start = max(failures)
request.axes_failures_since_start = failures_since_start
if failures_since_start > 1:
@ -126,11 +139,6 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
client_str,
)
cache_keys = get_client_cache_key(request, credentials)
for cache_key in cache_keys:
failures = self.cache.get(cache_key, default=0)
self.cache.set(cache_key, failures + 1, get_cache_timeout())
if (
settings.AXES_LOCK_OUT_AT_FAILURE
and failures_since_start >= get_failure_limit(request, credentials)
@ -166,7 +174,7 @@ class AxesCacheHandler(AbstractAxesHandler, AxesBaseHandler):
log.info("AXES: Successful login by %s.", client_str)
if settings.AXES_RESET_ON_SUCCESS:
cache_keys = get_client_cache_key(request, credentials)
cache_keys = get_client_cache_keys(request, credentials)
for cache_key in cache_keys:
failures_since_start = self.cache.get(cache_key, default=0)
self.cache.delete(cache_key)

View file

@ -1,26 +1,32 @@
from logging import getLogger
from typing import Optional
from typing import List, Optional
from django.db import transaction
from django.db.models import F, Sum, Value, Q
from django.db import router, transaction
from django.db.models import F, Q, QuerySet, Sum, Value
from django.db.models.functions import Concat
from django.http import HttpRequest
from django.utils import timezone
from axes.attempts import (
clean_expired_user_attempts,
get_user_attempts,
reset_user_attempts,
)
from axes.attempts import get_cool_off_threshold
from axes.conf import settings
from axes.handlers.base import AxesBaseHandler, AbstractAxesHandler
from axes.handlers.base import AbstractAxesHandler, AxesBaseHandler
from axes.helpers import (
get_client_parameters,
get_client_session_hash,
get_client_str,
get_client_username,
get_credentials,
get_failure_limit,
get_lockout_parameters,
get_query_str,
get_attempt_expiration,
)
from axes.models import (
AccessAttempt,
AccessAttemptExpiration,
AccessFailureLog,
AccessLog,
)
from axes.models import AccessLog, AccessAttempt, AccessFailureLog
from axes.signals import user_locked_out
log = getLogger(__name__)
@ -102,7 +108,7 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
return count
def get_failures(self, request, credentials: Optional[dict] = None) -> int:
attempts_list = get_user_attempts(request, credentials)
attempts_list = self.get_user_attempts(request, credentials)
attempt_count = max(
(
attempts.aggregate(Sum("failures_since_start"))[
@ -115,10 +121,10 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
return attempt_count
def user_login_failed(self, sender, credentials: dict, request=None, **kwargs):
"""When user login fails, save AccessFailureLog record in database,
"""
When user login fails, save AccessFailureLog record in database,
save AccessAttempt record in database, mark request with
lockout attribute and emit lockout signal.
"""
log.info("AXES: User login failed, running database handler for failure.")
@ -130,7 +136,7 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
return
# 1. database query: Clean up expired user attempts from the database before logging new attempts
clean_expired_user_attempts(request.axes_attempt_time)
self.clean_expired_user_attempts(request, credentials)
username = get_client_username(request, credentials)
client_str = get_client_str(
@ -164,12 +170,13 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
return
# 2. database query: Get or create access record with the new failure data
if settings.AXES_ONLY_USER_FAILURES and username is None:
lockout_parameters = get_lockout_parameters(request, credentials)
if lockout_parameters == ["username"] and username is None:
log.warning(
"AXES: Username is None and AXES_ONLY_USER_FAILURES is enabled, new record will NOT be created."
"AXES: Username is None and username is the only one lockout parameter, new record will NOT be created."
)
else:
with transaction.atomic():
with transaction.atomic(using=router.db_for_write(AccessAttempt)):
(
attempt,
created,
@ -218,6 +225,23 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
client_str,
)
if settings.AXES_USE_ATTEMPT_EXPIRATION:
if not hasattr(attempt, "expiration") or attempt.expiration is None:
log.debug(
"AXES: Creating new AccessAttemptExpiration for %s",
client_str,
)
attempt.expiration = AccessAttemptExpiration.objects.create(
access_attempt=attempt,
expires_at=get_attempt_expiration(request),
)
else:
attempt.expiration.expires_at = max(
get_attempt_expiration(request),
attempt.expiration.expires_at,
)
attempt.expiration.save()
# 3. or 4. database query: Calculate the current maximum failure number from the existing attempts
failures_since_start = self.get_failures(request, credentials)
request.axes_failures_since_start = failures_since_start
@ -241,7 +265,7 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
# 5. database entry: Log for ever the attempt in the AccessFailureLog
if settings.AXES_ENABLE_ACCESS_FAILURE_LOG:
with transaction.atomic():
with transaction.atomic(using=router.db_for_write(AccessFailureLog)):
AccessFailureLog.objects.create(
username=username,
ip_address=request.axes_ip_address,
@ -258,9 +282,6 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
When user logs in, update the AccessLog related to the user.
"""
# 1. database query: Clean up expired user attempts from the database
clean_expired_user_attempts(request.axes_attempt_time)
username = user.get_username()
credentials = get_credentials(username)
client_str = get_client_str(
@ -273,6 +294,9 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
log.info("AXES: Successful login by %s.", client_str)
# 1. database query: Clean up expired user attempts from the database
self.clean_expired_user_attempts(request, credentials)
if not settings.AXES_DISABLE_ACCESS_LOG:
# 2. database query: Insert new access logs with login time
AccessLog.objects.create(
@ -282,11 +306,14 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
http_accept=request.axes_http_accept,
path_info=request.axes_path_info,
attempt_time=request.axes_attempt_time,
# evaluate session hash here to ensure having the correct
# value which is stored on the backend
session_hash=get_client_session_hash(request),
)
if settings.AXES_RESET_ON_SUCCESS:
# 3. database query: Reset failed attempts for the logging in user
count = reset_user_attempts(request, credentials)
count = self.reset_user_attempts(request, credentials)
log.info(
"AXES: Deleted %d failed login attempts by %s from database.",
count,
@ -298,10 +325,8 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
When user logs out, update the AccessLog related to the user.
"""
# 1. database query: Clean up expired user attempts from the database
clean_expired_user_attempts(request.axes_attempt_time)
username = user.get_username() if user else None
credentials = get_credentials(username) if username else None
client_str = get_client_str(
username,
request.axes_ip_address,
@ -310,14 +335,117 @@ class AxesDatabaseHandler(AbstractAxesHandler, AxesBaseHandler):
request,
)
# 1. database query: Clean up expired user attempts from the database
self.clean_expired_user_attempts(request, credentials)
log.info("AXES: Successful logout by %s.", client_str)
if username and not settings.AXES_DISABLE_ACCESS_LOG:
# 2. database query: Update existing attempt logs with logout time
AccessLog.objects.filter(
username=username, logout_time__isnull=True
username=username,
logout_time__isnull=True,
# update only access log for given session
session_hash=get_client_session_hash(request),
).update(logout_time=request.axes_attempt_time)
def filter_user_attempts(
self, request: HttpRequest, credentials: Optional[dict] = None
) -> List[QuerySet]:
"""
Return a list querysets of AccessAttempts that match the given request and credentials.
"""
username = get_client_username(request, credentials)
filter_kwargs_list = get_client_parameters(
username,
request.axes_ip_address,
request.axes_user_agent,
request,
credentials,
)
attempts_list = [
AccessAttempt.objects.filter(**filter_kwargs)
for filter_kwargs in filter_kwargs_list
]
return attempts_list
def get_user_attempts(
self, request: HttpRequest, credentials: Optional[dict] = None # noqa
) -> List[QuerySet]:
"""
Get list of querysets with valid user attempts that match the given request and credentials.
"""
attempts_list = self.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)
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(
self,
request: Optional[HttpRequest] = None,
credentials: Optional[dict] = None, # noqa
) -> 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
if settings.AXES_USE_ATTEMPT_EXPIRATION:
threshold = timezone.now()
count, _ = AccessAttempt.objects.filter(
expiration__expires_at__lte=threshold
).delete()
log.info(
"AXES: Cleaned up %s expired access attempts from database that expiry were older than %s",
count,
threshold,
)
else:
threshold = get_cool_off_threshold(request)
count, _ = AccessAttempt.objects.filter(
attempt_time__lte=threshold
).delete()
log.info(
"AXES: Cleaned up %s expired access attempts from database that were older than %s",
count,
threshold,
)
return count
def reset_user_attempts(
self, request: HttpRequest, credentials: Optional[dict] = None
) -> int:
"""
Reset all user attempts that match the given request and credentials.
"""
attempts_list = self.filter_user_attempts(request, credentials)
count = 0
for attempts in attempts_list:
_count, _ = attempts.delete()
count += _count
log.info("AXES: Reset %s access attempts from database.", count)
return count
def post_save_access_attempt(self, instance, **kwargs):
"""
Handles the ``axes.models.AccessAttempt`` object post save signal.

View file

@ -2,7 +2,7 @@ from axes.handlers.base import AxesHandler
from typing import Optional
class AxesTestHandler(AxesHandler): # pylint: disable=unused-argument
class AxesTestHandler(AxesHandler):
"""
Signal handler implementation that does nothing, ideal for a test suite.
"""

View file

@ -1,14 +1,14 @@
from datetime import timedelta
from datetime import timedelta, datetime
from hashlib import sha256
from logging import getLogger
from string import Template
from typing import Callable, Optional, Type, Union
from typing import Callable, Optional, Type, Union, List
from urllib.parse import urlencode
import ipware.ip
from django.core.cache import caches, BaseCache
from django.core.cache import BaseCache, caches
from django.http import HttpRequest, HttpResponse, JsonResponse, QueryDict
from django.shortcuts import render, redirect
from django.shortcuts import redirect, render
from django.utils.encoding import force_bytes
from django.utils.module_loading import import_string
from axes.conf import settings
@ -16,6 +16,13 @@ from axes.models import AccessBase
log = getLogger(__name__)
try:
import ipware.ip
IPWARE_INSTALLED = True
except ImportError:
IPWARE_INSTALLED = False
def get_cache() -> BaseCache:
"""
@ -25,32 +32,33 @@ def get_cache() -> BaseCache:
return caches[getattr(settings, "AXES_CACHE", "default")]
def get_cache_timeout() -> Optional[int]:
def get_cache_timeout(request: Optional[HttpRequest] = None) -> Optional[int]:
"""
Return the cache timeout interpreted from settings.AXES_COOLOFF_TIME.
The cache timeout can be either None if not configured or integer of seconds if configured.
Notice that the settings.AXES_COOLOFF_TIME can be None, timedelta, integer, callable, or str path,
Notice that the settings.AXES_COOLOFF_TIME can be None, timedelta, float, integer, callable, or str path,
and this function offers a unified _integer or None_ representation of that configuration
for use with the Django cache backends.
"""
cool_off = get_cool_off()
cool_off = get_cool_off(request)
if cool_off is None:
return None
return int(cool_off.total_seconds())
def get_cool_off() -> Optional[timedelta]:
def get_cool_off(request: Optional[HttpRequest] = None) -> 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.
Notice that the settings.AXES_COOLOFF_TIME is either None, timedelta, integer/float of hours,
a path to a callable or a callable taking 1 argument (the request). 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.
"""
@ -62,9 +70,10 @@ def get_cool_off() -> Optional[timedelta]:
if isinstance(cool_off, float):
return timedelta(minutes=cool_off * 60)
if isinstance(cool_off, str):
return import_string(cool_off)()
cool_off_func = import_string(cool_off)
return cool_off_func(request)
if callable(cool_off):
return cool_off() # pylint: disable=not-callable
return cool_off(request) # pylint: disable=not-callable
return cool_off
@ -92,6 +101,23 @@ def get_cool_off_iso8601(delta: timedelta) -> str:
return f"P{days_str}"
def get_attempt_expiration(request: Optional[HttpRequest] = None) -> datetime:
"""
Get threshold for fetching access attempts from the database.
"""
cool_off = get_cool_off(request)
if cool_off is None:
raise TypeError(
"Cool off threshold can not be calculated with settings.AXES_COOLOFF_TIME set to None"
)
attempt_time = request.axes_attempt_time # type: ignore[union-attr]
if attempt_time is None:
return datetime.now() + cool_off
return attempt_time + cool_off
def get_credentials(username: Optional[str] = None, **kwargs) -> dict:
"""
Calculate credentials for Axes to use internally from given username and kwargs.
@ -138,7 +164,7 @@ def get_client_username(
log.debug(
"Using parameter credentials to get username with key settings.AXES_USERNAME_FORM_FIELD"
)
return credentials.get(settings.AXES_USERNAME_FORM_FIELD, None)
return credentials.get(settings.AXES_USERNAME_FORM_FIELD, None) # type: ignore[return-value]
log.debug(
"Using parameter request.POST to get username with key settings.AXES_USERNAME_FORM_FIELD"
@ -148,23 +174,55 @@ def get_client_username(
return request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
def get_client_ip_address(request: HttpRequest) -> str:
def get_client_ip_address(
request: HttpRequest,
use_ipware: Optional[bool] = None,
) -> Optional[str]:
"""
Get client IP address as configured by the user.
The django-ipware package is used for address resolution
and parameters can be configured in the Axes package.
The order of preference for address resolution is as follows:
1. If configured, use ``AXES_CLIENT_IP_CALLABLE``, and supply ``request`` as argument
2. If available, use django-ipware package (parameters can be configured in the Axes package)
3. Use ``request.META.get('REMOTE_ADDR', None)`` as a fallback
:param request: incoming Django ``HttpRequest`` or similar object from authentication backend or other source
"""
client_ip_address, _ = ipware.ip.get_client_ip(
request,
proxy_order=settings.AXES_PROXY_ORDER,
proxy_count=settings.AXES_PROXY_COUNT,
proxy_trusted_ips=settings.AXES_PROXY_TRUSTED_IPS,
request_header_order=settings.AXES_META_PRECEDENCE_ORDER,
)
if settings.AXES_CLIENT_IP_CALLABLE:
log.debug("Using settings.AXES_CLIENT_IP_CALLABLE to get client IP address")
return client_ip_address
if callable(settings.AXES_CLIENT_IP_CALLABLE):
return settings.AXES_CLIENT_IP_CALLABLE( # pylint: disable=not-callable
request
)
if isinstance(settings.AXES_CLIENT_IP_CALLABLE, str):
return import_string(settings.AXES_CLIENT_IP_CALLABLE)(request)
raise TypeError(
"settings.AXES_CLIENT_IP_CALLABLE needs to be a string, callable, or None."
)
# Resolve using django-ipware from a configuration flag that can be set to False to explicitly disable
# this is added to both enable or disable the branch when ipware is installed in the test environment
if use_ipware is None:
use_ipware = IPWARE_INSTALLED
if use_ipware:
log.debug("Using django-ipware to get client IP address")
client_ip_address, _ = ipware.ip.get_client_ip(
request,
proxy_order=settings.AXES_IPWARE_PROXY_ORDER,
proxy_count=settings.AXES_IPWARE_PROXY_COUNT,
proxy_trusted_ips=settings.AXES_IPWARE_PROXY_TRUSTED_IPS,
request_header_order=settings.AXES_IPWARE_META_PRECEDENCE_ORDER,
)
return client_ip_address
log.debug(
"Using request.META.get('REMOTE_ADDR', None) fallback method to get client IP address"
)
return request.META.get("REMOTE_ADDR", None)
def get_client_user_agent(request: HttpRequest) -> str:
@ -179,7 +237,33 @@ def get_client_http_accept(request: HttpRequest) -> str:
return request.META.get("HTTP_ACCEPT", "<unknown>")[:1025]
def get_client_parameters(username: str, ip_address: str, user_agent: str) -> list:
def get_lockout_parameters(
request_or_attempt: Union[HttpRequest, AccessBase],
credentials: Optional[dict] = None,
) -> List[Union[str, List[str]]]:
if callable(settings.AXES_LOCKOUT_PARAMETERS):
return settings.AXES_LOCKOUT_PARAMETERS(request_or_attempt, credentials)
if isinstance(settings.AXES_LOCKOUT_PARAMETERS, str):
return import_string(settings.AXES_LOCKOUT_PARAMETERS)(
request_or_attempt, credentials
)
if isinstance(settings.AXES_LOCKOUT_PARAMETERS, list):
return settings.AXES_LOCKOUT_PARAMETERS
raise TypeError(
"settings.AXES_LOCKOUT_PARAMETERS needs to be a callable or iterable"
)
def get_client_parameters(
username: str,
ip_address: str,
user_agent: str,
request_or_attempt: Union[HttpRequest, AccessBase],
credentials: Optional[dict] = None,
) -> List[dict]:
"""
Get query parameters for filtering AccessAttempt queryset.
@ -188,29 +272,39 @@ def get_client_parameters(username: str, ip_address: str, user_agent: str) -> li
Returns list of dict, every item of list are separate parameters
"""
lockout_parameters = get_lockout_parameters(request_or_attempt, credentials)
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}]
parameters_dict = {
"username": username,
"ip_address": ip_address,
"user_agent": user_agent,
}
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})
filter_kwargs = []
return filter_query
for parameter in lockout_parameters:
try:
if isinstance(parameter, str):
filter_kwarg = {parameter: parameters_dict[parameter]}
else:
filter_kwarg = {
combined_parameter: parameters_dict[combined_parameter]
for combined_parameter in parameter
}
filter_kwargs.append(filter_kwarg)
except KeyError as e:
error_msg = (
f"{e} lockout parameter is not allowed. "
f"Allowed parameters: {', '.join(parameters_dict.keys())}"
)
log.exception(error_msg)
raise ValueError(error_msg) from e
return filter_kwargs
def make_cache_key_list(filter_kwargs_list):
def make_cache_key_list(filter_kwargs_list: List[dict]) -> List[str]:
cache_keys = []
for filter_kwargs in filter_kwargs_list:
cache_key_components = "".join(
@ -221,10 +315,10 @@ def make_cache_key_list(filter_kwargs_list):
return cache_keys
def get_client_cache_key(
def get_client_cache_keys(
request_or_attempt: Union[HttpRequest, AccessBase],
credentials: Optional[dict] = None,
) -> str:
) -> List[str]:
"""
Build cache key name from request or AccessAttempt object.
@ -242,7 +336,9 @@ def get_client_cache_key(
ip_address = get_client_ip_address(request_or_attempt)
user_agent = get_client_user_agent(request_or_attempt)
filter_kwargs_list = get_client_parameters(username, ip_address, user_agent)
filter_kwargs_list = get_client_parameters(
username, ip_address, user_agent, request_or_attempt, credentials
)
return make_cache_key_list(filter_kwargs_list)
@ -285,11 +381,11 @@ def get_client_str(
client_dict["user_agent"] = user_agent
else:
# Other modes initialize the attributes that are used for the actual lockouts
client_list = get_client_parameters(username, ip_address, user_agent)
client_list = get_client_parameters(username, ip_address, user_agent, request)
client_dict = {}
for client in client_list:
client_dict.update(client)
client_dict = cleanse_parameters(client_dict.copy())
# Path info is always included as last component in the client string for traceability purposes
if path_info and isinstance(path_info, (tuple, list)):
path_info = path_info[0]
@ -366,15 +462,27 @@ def get_lockout_message() -> str:
def get_lockout_response(
request: HttpRequest, credentials: Optional[dict] = None
request: HttpRequest,
original_response: Optional[HttpResponse] = None,
credentials: Optional[dict] = None,
) -> HttpResponse:
if settings.AXES_LOCKOUT_CALLABLE:
if callable(settings.AXES_LOCKOUT_CALLABLE):
return settings.AXES_LOCKOUT_CALLABLE( # pylint: disable=not-callable
request, credentials
)
# Try calling with 3 args, fallback to 2 for backward compatibility
try:
return settings.AXES_LOCKOUT_CALLABLE(
request, original_response, credentials
)
except TypeError:
# Fallback: old signature without original_response
return settings.AXES_LOCKOUT_CALLABLE(request, credentials)
if isinstance(settings.AXES_LOCKOUT_CALLABLE, str):
return import_string(settings.AXES_LOCKOUT_CALLABLE)(request, credentials)
callable_obj = import_string(settings.AXES_LOCKOUT_CALLABLE)
# Try calling with 3 args, fallback to 2 for backward compatibility
try:
return callable_obj(request, original_response, credentials)
except TypeError:
return callable_obj(request, credentials)
raise TypeError(
"settings.AXES_LOCKOUT_CALLABLE needs to be a string, callable, or None."
)
@ -385,7 +493,7 @@ def get_lockout_response(
"username": get_client_username(request, credentials) or "",
}
cool_off = get_cool_off()
cool_off = get_cool_off(request)
if cool_off:
context.update(
{
@ -398,13 +506,13 @@ def get_lockout_response(
if request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest":
json_response = JsonResponse(context, status=status)
json_response[
"Access-Control-Allow-Origin"
] = settings.AXES_ALLOWED_CORS_ORIGINS
json_response["Access-Control-Allow-Origin"] = (
settings.AXES_ALLOWED_CORS_ORIGINS
)
json_response["Access-Control-Allow-Methods"] = "POST, OPTIONS"
json_response[
"Access-Control-Allow-Headers"
] = "Origin, Content-Type, Accept, Authorization, x-requested-with"
json_response["Access-Control-Allow-Headers"] = (
"Origin, Content-Type, Accept, Authorization, x-requested-with"
)
return json_response
if settings.AXES_LOCKOUT_TEMPLATE:
@ -538,3 +646,24 @@ def toggleable(func) -> Callable:
return func(*args, **kwargs)
return inner
def get_client_session_hash(request: HttpRequest) -> str:
"""
Get client session and returns the SHA256 hash of session key, forcing session creation if required.
If no session is available on request returns an empty string.
"""
try:
session = request.session
except AttributeError:
# when no session is available just return an empty string
return ""
# ensure that a session key exists at this point
# because session middleware usually creates the session key at the end
# of request cycle
if session.session_key is None:
session.create()
return sha256(force_bytes(session.session_key)).hexdigest()

Binary file not shown.

View file

@ -0,0 +1,106 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-05-30 15:16+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#: admin.py:27
msgid "Form Data"
msgstr "بيانات النموذج"
#: admin.py:28 admin.py:65 admin.py:100
msgid "Meta Data"
msgstr "البيانات الوصفية"
#: conf.py:97
msgid "Account locked: too many login attempts. Please try again later."
msgstr "الحساب مغلق: محاولات تسجيل دخول كثيرة جدًا. الرجاء معاودة المحاولة في وقت لاحق."
#: conf.py:105
msgid ""
"Account locked: too many login attempts. Contact an admin to unlock your "
"account."
msgstr "الحساب مغلق: محاولات تسجيل دخول كثيرة جدًا. اتصل بمسؤول لفتح حسابك."
#: models.py:6
msgid "User Agent"
msgstr "وكيل المستخدم"
#: models.py:8
msgid "IP Address"
msgstr "عنوان IP"
#: models.py:10
msgid "Username"
msgstr "اسم المستخدم"
#: models.py:12
msgid "HTTP Accept"
msgstr "قبول HTTP"
#: models.py:14
msgid "Path"
msgstr "معلومات المسار"
#: models.py:16
msgid "Attempt Time"
msgstr "وقت المحاولة"
#: models.py:26
msgid "Access lock out"
msgstr "مقيد من الدخول"
#: models.py:34
msgid "access failure"
msgstr "سجل دخول فاشلة"
#: models.py:35
msgid "access failures"
msgstr "سجلات دخول فاشلة"
#: models.py:39
msgid "GET Data"
msgstr "GET بيانات"
#: models.py:41
msgid "POST Data"
msgstr "POST بيانات"
#: models.py:43
msgid "Failed Logins"
msgstr "عمليات تسجيل دخول فاشلة"
#: models.py:49
msgid "access attempt"
msgstr "محاولة دخول"
#: models.py:50
msgid "access attempts"
msgstr "محاولات دخول"
#: models.py:55
msgid "Logout Time"
msgstr "وقت تسجيل الخروج"
#: models.py:61
msgid "access log"
msgstr "سجل الدخول"
#: models.py:62
msgid "access logs"
msgstr "سجلات الدخول"

Binary file not shown.

View file

@ -0,0 +1,109 @@
# ترجمه فارسی برای django-axes
# Copyright (C) 2025 jazzband
# This file is distributed under the same license as the django-axes package.
# AmirAli Bahramjerdi <amiralibahramjerdi@gmail.com>, 2025.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: django-axes\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-05-16 23:28+0330\n"
"PO-Revision-Date: 2025-05-16 23:30+0330\n"
"Last-Translator: AmirAli Bahramjerdi <amiralibahramjerdi@gmail.com>"
"Language-Team: فارسی <fa@li.org>\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: admin.py:27
msgid "Form Data"
msgstr "داده‌های فرم"
#: admin.py:28 admin.py:65 admin.py:100
msgid "Meta Data"
msgstr "فراداده"
#: conf.py:109
msgid "Account locked: too many login attempts. Please try again later."
msgstr "حساب کاربری قفل شد: تلاش‌های زیادی برای ورود انجام شده است. لطفاً بعداً دوباره امتحان کنید."
#: conf.py:117
msgid ""
"Account locked: too many login attempts. Contact an admin to unlock your "
"account."
msgstr "حساب کاربری قفل شد: تلاش‌های زیادی برای ورود انجام شده است. برای باز کردن حساب با مدیر تماس بگیرید."
#: models.py:6
msgid "User Agent"
msgstr "عامل کاربر (User Agent)"
#: models.py:8
msgid "IP Address"
msgstr "آدرس IP"
#: models.py:10
msgid "Username"
msgstr "نام کاربری"
#: models.py:12
msgid "HTTP Accept"
msgstr "پذیرش HTTP"
#: models.py:14
msgid "Path"
msgstr "مسیر"
#: models.py:16
msgid "Attempt Time"
msgstr "زمان تلاش"
#: models.py:26
msgid "Access lock out"
msgstr "قفل دسترسی"
#: models.py:34
msgid "access failure"
msgstr "شکست در دسترسی"
#: models.py:35
msgid "access failures"
msgstr "شکست‌های دسترسی"
#: models.py:39
msgid "GET Data"
msgstr "داده‌های GET"
#: models.py:41
msgid "POST Data"
msgstr "داده‌های POST"
#: models.py:43
msgid "Failed Logins"
msgstr "ورودهای ناموفق"
#: models.py:49
msgid "access attempt"
msgstr "تلاش برای دسترسی"
#: models.py:50
msgid "access attempts"
msgstr "تلاش‌های دسترسی"
#: models.py:55
msgid "Logout Time"
msgstr "زمان خروج"
#: models.py:56
msgid "Session key hash (sha256)"
msgstr "هش کلید نشست (sha256)"
#: models.py:62
msgid "access log"
msgstr "گزارش دسترسی"
#: models.py:63
msgid "access logs"
msgstr "گزارش‌های دسترسی"

Binary file not shown.

View file

@ -0,0 +1,109 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-06 05:21-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: admin.py:27
msgid "Form Data"
msgstr "Données de formulaire"
#: admin.py:28 admin.py:65 admin.py:100
msgid "Meta Data"
msgstr "Métadonnées"
#: conf.py:108
msgid "Account locked: too many login attempts. Please try again later."
msgstr ""
"Compte verrouillé: trop de tentatives de connexion. Veuillez réessayer plus "
"tard."
#: conf.py:116
msgid ""
"Account locked: too many login attempts. Contact an admin to unlock your "
"account."
msgstr ""
"Compte verrouillé: trop de tentatives de connexion. Contactez un "
"administrateur pour déverrouiller votre compte."
#: models.py:6
msgid "User Agent"
msgstr "User Agent"
#: models.py:8
msgid "IP Address"
msgstr "Adresse IP"
#: models.py:10
msgid "Username"
msgstr "Nom d'utilisateur"
#: models.py:12
msgid "HTTP Accept"
msgstr "HTTP Accept"
#: models.py:14
msgid "Path"
msgstr "Chemin"
#: models.py:16
msgid "Attempt Time"
msgstr "Date de la tentative"
#: models.py:26
msgid "Access lock out"
msgstr "Verrouillage de l'accès"
#: models.py:34
msgid "access failure"
msgstr "échec de connexion"
#: models.py:35
msgid "access failures"
msgstr "échecs de connexion"
#: models.py:39
msgid "GET Data"
msgstr "Données GET"
#: models.py:41
msgid "POST Data"
msgstr "Données POST"
#: models.py:43
msgid "Failed Logins"
msgstr "Nombre d'échecs"
#: models.py:49
msgid "access attempt"
msgstr "tentative de connexion"
#: models.py:50
msgid "access attempts"
msgstr "tentatives de connexion"
#: models.py:55
msgid "Logout Time"
msgstr "Date de la déconnexion"
#: models.py:61
msgid "access log"
msgstr "connexion"
#: models.py:62
msgid "access logs"
msgstr "connexions"

Binary file not shown.

View file

@ -0,0 +1,105 @@
# Indonesian translation for django-axes.
# Copyright (C) 2023
# This file is distributed under the same license as the django-axes package.
# Kira <kiraware@github.com>, 2023.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: django-axes 6.0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-06-30 09:21+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Kira <kiraware@github.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: .\axes\admin.py:27
msgid "Form Data"
msgstr "Data Formulir"
#: .\axes\admin.py:28 .\axes\admin.py:65 .\axes\admin.py:100
msgid "Meta Data"
msgstr "Meta Data"
#: .\axes\conf.py:108
msgid "Account locked: too many login attempts. Please try again later."
msgstr "Akun terkunci: terlalu banyak percobaan login. Silakan coba lagi nanti."
#: .\axes\conf.py:116
msgid ""
"Account locked: too many login attempts. Contact an admin to unlock your "
"account."
msgstr "Akun terkunci: terlalu banyak percobaan login. Hubungi admin untuk"
" membuka kunci akun"
#: .\axes\models.py:6
msgid "User Agent"
msgstr "User Agent"
#: .\axes\models.py:8
msgid "IP Address"
msgstr "Alamat IP"
#: .\axes\models.py:10
msgid "Username"
msgstr "Nama Pengguna"
#: .\axes\models.py:12
msgid "HTTP Accept"
msgstr "HTTP Accept"
#: .\axes\models.py:14
msgid "Path"
msgstr "Path"
#: .\axes\models.py:16
msgid "Attempt Time"
msgstr "Waktu Percobaan"
#: .\axes\models.py:26
msgid "Access lock out"
msgstr "Akses terkunci"
#: .\axes\models.py:34
msgid "access failure"
msgstr "kegagalan akses"
#: .\axes\models.py:35
msgid "access failures"
msgstr "kegagalan akses"
#: .\axes\models.py:39
msgid "GET Data"
msgstr "Data GET"
#: .\axes\models.py:41
msgid "POST Data"
msgstr "Data POST"
#: .\axes\models.py:43
msgid "Failed Logins"
msgstr "Login Gagal"
#: .\axes\models.py:49
msgid "access attempt"
msgstr "upaya akses"
#: .\axes\models.py:50
msgid "access attempts"
msgstr "upaya akses"
#: .\axes\models.py:55
msgid "Logout Time"
msgstr "Waktu Logout"
#: .\axes\models.py:61
msgid "access log"
msgstr "log akses"
#: .\axes\models.py:62
msgid "access logs"
msgstr "log akses"

Binary file not shown.

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-11 12:20+0300\n"
"POT-Creation-Date: 2023-05-13 12:36+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,80 +18,92 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: axes/admin.py:38
#: axes/admin.py:27
msgid "Form Data"
msgstr "Данные формы"
#: axes/admin.py:41 axes/admin.py:95
#: axes/admin.py:28 axes/admin.py:65 axes/admin.py:100
msgid "Meta Data"
msgstr "Метаданные"
#: axes/conf.py:58
#: axes/conf.py:99
msgid "Account locked: too many login attempts. Please try again later."
msgstr ""
"Учетная запись заблокирована: слишком много попыток входа. "
"Повторите попытку позже."
"Учетная запись заблокирована: слишком много попыток входа. Повторите попытку "
"позже."
#: axes/conf.py:61
#: axes/conf.py:107
msgid ""
"Account locked: too many login attempts. Contact an admin to unlock your "
"account."
msgstr ""
"Учетная запись заблокирована: слишком много попыток входа. "
"Обратитесь к администратору для разблокирования учетной записи."
"Учетная запись заблокирована: слишком много попыток входа. Свяжитесь с "
"администратором, чтобы разблокировать учетную запись."
#: axes/models.py:9
#: axes/models.py:6
msgid "User Agent"
msgstr "Браузер пользователя"
msgstr "User Agent"
#: axes/models.py:15
#: axes/models.py:8
msgid "IP Address"
msgstr "Адрес IP"
msgstr "IP Адрес"
#: axes/models.py:21
#: axes/models.py:10
msgid "Username"
msgstr "Пользователь"
msgstr "Имя пользователя"
#: axes/models.py:35
#: axes/models.py:12
msgid "HTTP Accept"
msgstr "Запрос HTTP"
msgstr "HTTP Accept"
#: axes/models.py:40
#: axes/models.py:14
msgid "Path"
msgstr "Путь"
#: axes/models.py:45
#: axes/models.py:16
msgid "Attempt Time"
msgstr "Время входа"
msgstr "Время попытки входа"
#: axes/models.py:57
#: axes/models.py:26
msgid "Access lock out"
msgstr "Доступ запрещен"
#: axes/models.py:34
msgid "access failure"
msgstr "Ошибка доступа"
#: axes/models.py:35
msgid "access failures"
msgstr "Ошибки доступа"
#: axes/models.py:39
msgid "GET Data"
msgstr "Данные GET-запроса"
#: axes/models.py:61
#: axes/models.py:41
msgid "POST Data"
msgstr "Данные POST-запроса"
#: axes/models.py:65
#: axes/models.py:43
msgid "Failed Logins"
msgstr "Ошибочные попытки"
#: axes/models.py:76
#: axes/models.py:49
msgid "access attempt"
msgstr "Запись о попытке доступа"
#: axes/models.py:77
#: axes/models.py:50
msgid "access attempts"
msgstr "Попытки доступа"
#: axes/models.py:81
#: axes/models.py:55
msgid "Logout Time"
msgstr "Время выхода"
#: axes/models.py:90
#: axes/models.py:61
msgid "access log"
msgstr "Запись о доступе"
#: axes/models.py:91
#: axes/models.py:62
msgid "access logs"
msgstr "Логи доступа"

Binary file not shown.

View file

@ -0,0 +1,19 @@
from django.core.management.base import BaseCommand
from axes.utils import reset
class Command(BaseCommand):
help = "Reset all access attempts and lockouts for a given IP address and username"
def add_arguments(self, parser):
parser.add_argument("ip", type=str)
parser.add_argument("username", type=str)
def handle(self, *args, **options):
count = reset(ip=options["ip"], username=options["username"])
if count:
self.stdout.write(f"{count} attempts removed.")
else:
self.stdout.write("No attempts found.")

View file

@ -1,5 +1,6 @@
from typing import Callable
from asgiref.sync import iscoroutinefunction, markcoroutinefunction, sync_to_async
from django.conf import settings
from django.http import HttpRequest, HttpResponse
@ -30,15 +31,37 @@ class AxesMiddleware:
- ``AXES_PERMALOCK_MESSAGE``.
"""
async_capable = True
sync_capable = True
def __init__(self, get_response: Callable) -> None:
self.get_response = get_response
if iscoroutinefunction(self.get_response):
markcoroutinefunction(self)
def __call__(self, request: HttpRequest) -> HttpResponse:
# Exit out to async mode, if needed
if iscoroutinefunction(self):
return self.__acall__(request)
response = self.get_response(request)
if settings.AXES_ENABLED:
if getattr(request, "axes_locked_out", None):
credentials = getattr(request, "axes_credentials", None)
response = get_lockout_response(request, response, credentials) # type: ignore
return response
async def __acall__(self, request: HttpRequest) -> HttpResponse:
response = await self.get_response(request)
if settings.AXES_ENABLED:
if getattr(request, "axes_locked_out", None):
credentials = getattr(request, "axes_credentials", None)
response = get_lockout_response(request, credentials) # type: ignore
response = await sync_to_async(
get_lockout_response, thread_sensitive=True
)(
request, credentials
) # type: ignore
return response

View file

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = []
operations = [

View file

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("axes", "0001_initial")]
operations = [

View file

@ -2,7 +2,6 @@ from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [("axes", "0002_auto_20151217_2044")]
operations = [

View file

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [("axes", "0003_auto_20160322_0929")]
operations = [

View file

@ -2,7 +2,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("axes", "0004_auto_20181024_1538")]
operations = [migrations.RemoveField(model_name="accessattempt", name="trusted")]

View file

@ -4,7 +4,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [("axes", "0005_remove_accessattempt_trusted")]
operations = [migrations.RemoveField(model_name="accesslog", name="trusted")]

View file

@ -1,36 +1,43 @@
# Generated by Django 3.2.7 on 2021-09-13 15:16
from django.db import migrations
from django.db import migrations, router
from django.db.models import Count
def deduplicate_attempts(apps, schema_editor):
AccessAttempt = apps.get_model("axes", "AccessAttempt")
db_alias = schema_editor.connection.alias
if db_alias != router.db_for_write(AccessAttempt):
return
duplicated_attempts = (
AccessAttempt.objects.values("username", "user_agent", "ip_address")
AccessAttempt.objects.using(db_alias)
.values("username", "user_agent", "ip_address")
.annotate(Count("id"))
.order_by()
.filter(id__count__gt=1)
)
for attempt in duplicated_attempts:
redundant_attempts = AccessAttempt.objects.filter(
redundant_attempts = AccessAttempt.objects.using(db_alias).filter(
username=attempt["username"],
user_agent=attempt["user_agent"],
ip_address=attempt["ip_address"],
)[1:]
for redundant_attempt in redundant_attempts:
redundant_attempt.delete()
redundant_attempt.delete(using=db_alias)
class Migration(migrations.Migration):
dependencies = [
("axes", "0006_remove_accesslog_trusted"),
]
operations = [
migrations.RunPython(deduplicate_attempts),
migrations.RunPython(
deduplicate_attempts, reverse_code=migrations.RunPython.noop
),
migrations.AlterUniqueTogether(
name="accessattempt",
unique_together={("username", "ip_address", "user_agent")},

View file

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("axes", "0007_alter_accessattempt_unique_together"),
]

View file

@ -0,0 +1,22 @@
# Generated by Django 4.2.2 on 2024-04-30 07:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("axes", "0008_accessfailurelog"),
]
operations = [
migrations.AddField(
model_name="accesslog",
name="session_hash",
field=models.CharField(
blank=True,
default="",
max_length=64,
verbose_name="Session key hash (sha256)",
),
),
]

View file

@ -0,0 +1,41 @@
# Generated by Django 5.2.1 on 2025-06-10 20:21
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("axes", "0009_add_session_hash"),
]
operations = [
migrations.CreateModel(
name="AccessAttemptExpiration",
fields=[
(
"access_attempt",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
primary_key=True,
related_name="expiration",
serialize=False,
to="axes.accessattempt",
verbose_name="Access Attempt",
),
),
(
"expires_at",
models.DateTimeField(
help_text="The time when access attempt expires and is no longer valid.",
verbose_name="Expires At",
),
),
],
options={
"verbose_name": "access attempt expiration",
"verbose_name_plural": "access attempt expirations",
},
),
]

View file

@ -51,8 +51,29 @@ class AccessAttempt(AccessBase):
unique_together = [["username", "ip_address", "user_agent"]]
class AccessAttemptExpiration(models.Model):
access_attempt = models.OneToOneField(
AccessAttempt,
primary_key=True,
on_delete=models.CASCADE,
related_name="expiration",
verbose_name=_("Access Attempt"),
)
expires_at = models.DateTimeField(
_("Expires At"),
help_text=_("The time when access attempt expires and is no longer valid."),
)
class Meta:
verbose_name = _("access attempt expiration")
verbose_name_plural = _("access attempt expirations")
class AccessLog(AccessBase):
logout_time = models.DateTimeField(_("Logout Time"), null=True, blank=True)
session_hash = models.CharField(
_("Session key hash (sha256)"), default="", blank=True, max_length=64
)
def __str__(self):
return f"Access Log for {self.username} @ {self.attempt_time}"

View file

@ -10,9 +10,8 @@ from typing import Optional
from django.http import HttpRequest
from axes.conf import settings
from axes.handlers.proxy import AxesProxyHandler
from axes.helpers import get_client_ip_address
from axes.helpers import get_client_ip_address, get_lockout_parameters
log = getLogger(__name__)
@ -37,23 +36,38 @@ def reset_request(request: HttpRequest) -> int:
This utility method is meant to be used from the CLI or via Python API.
"""
lockout_paramaters = get_lockout_parameters(request)
ip: Optional[str] = get_client_ip_address(request)
username = request.GET.get("username", None)
ip_or_username = settings.AXES_LOCK_OUT_BY_USER_OR_IP
if settings.AXES_ONLY_USER_FAILURES:
ip_required = False
username_required = False
ip_and_username = False
for param in lockout_paramaters:
# hack: in works with all iterables, including strings
# so this checks works with separate parameters
# and with parameters combinations
if "username" in param and "ip_address" in param:
ip_and_username = True
ip_required = True
username_required = True
break
if "username" in param:
username_required = True
elif "ip_address" in param:
ip_required = True
ip_or_username = not ip_and_username and ip_required and username_required
if not ip_required:
ip = None
elif not (
settings.AXES_LOCK_OUT_BY_USER_OR_IP
or settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP
):
if not username_required:
username = None
if not ip and not username:
return 0
# We don't want to reset everything, if there is some wrong request parameter
# if settings.AXES_USE_USER_AGENT:
# TODO: reset based on user_agent?
return reset(ip, username, ip_or_username)

View file

@ -3,11 +3,11 @@
Requirements
============
Axes requires a supported Django version and runs on Python versions 3.6 and above.
Axes requires a supported Django version and runs on Python versions 3.9 and above.
Refer to the project source code repository in
`GitHub <https://github.com/jazzband/django-axes/>`_ and see the
`Tox configuration <https://github.com/jazzband/django-axes/blob/master/tox.ini>`_ and
`pyproject.toml file <https://github.com/jazzband/django-axes/blob/master/pyproject.toml>`_ and
`Python package definition <https://github.com/jazzband/django-axes/blob/master/setup.py>`_
to check if your Django and Python version are supported.

View file

@ -5,7 +5,8 @@ Installation
Axes is easy to install from the PyPI package::
$ pip install django-axes
$ pip install django-axes[ipware] # use django-ipware for resolving client IP addresses OR
$ pip install django-axes # implement and configure custom AXES_CLIENT_IP_CALLABLE
After installing the package, the project settings need to be configured.
@ -23,16 +24,21 @@ After installing the package, the project settings need to be configured.
'axes',
]
**2.** Add ``axes.backends.AxesBackend`` to the top of ``AUTHENTICATION_BACKENDS``::
**2.** Add ``axes.backends.AxesStandaloneBackend`` to the top of ``AUTHENTICATION_BACKENDS``::
AUTHENTICATION_BACKENDS = [
# AxesBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
'axes.backends.AxesBackend',
# AxesStandaloneBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
'axes.backends.AxesStandaloneBackend',
# Django ModelBackend is the default authentication backend.
'django.contrib.auth.backends.ModelBackend',
]
For backwards compatibility, ``AxesBackend`` can be used in place of ``AxesStandaloneBackend``.
The only difference is that ``AxesBackend`` also provides the permissions-checking functionality
of Django's ``ModelBackend`` behind the scenes. We recommend using ``AxesStandaloneBackend``
if you have any custom logic to override Django's standard permissions checks.
**3.** Add ``axes.middleware.AxesMiddleware`` to your list of ``MIDDLEWARE``::
MIDDLEWARE = [
@ -50,6 +56,9 @@ After installing the package, the project settings need to be configured.
# on failed user authentication attempts from login views.
# If you do not want Axes to override the authentication response
# you can skip installing the middleware and use your own views.
# AxesMiddleware runs during the reponse phase. It does not conflict
# with middleware that runs in the request phase like
# django.middleware.cache.FetchFromCacheMiddleware.
'axes.middleware.AxesMiddleware',
]
@ -69,6 +78,90 @@ Many people have different configurations for their development and production e
and running the application with misconfigured settings can prevent security features from working.
Version 8 breaking changes and upgrading from django-axes version 7
-------------------------------------------------------------------
Some database related utility functions have moved from ``axes.helpers`` to ``axes.handlers.database`` module and under the ``axes.handlers.database.AxesDatabaseHandler`` class.
Version 7 breaking changes and upgrading from django-axes version 6
-------------------------------------------------------------------
If you use ``settings.AXES_COOLOFF_TIME`` for configuring a callable that returns the cooloff time, it needs to accept at minimum a ``request`` argument of type ``HttpRequest`` from version 7 onwards. Example: ``AXES_COOLOFF_TIME = lambda request: timedelta(hours=2)`` (new call signature) instead of ``AXES_COOLOFF_TIME = lambda: timedelta(hours=2)`` (old cal signature).
Please see configuration documentation and `jazzband/django-axes#1222 <https://github.com/jazzband/django-axes/pull/1222>`_ for reference.
Version 6 breaking changes and upgrading from django-axes version 5
-------------------------------------------------------------------
If you have not specialized ``django-axes`` configuration in any way
you do not have to update any of the configuration.
The instructions apply to users who have configured ``django-axes`` in their projects
and have used flags that are deprecated. The deprecated flags will be removed in the future
but are compatible for at least version 6.0 of ``django-axes``.
The following flags and configuration have changed:
``django-ipware`` has become an optional dependency.
To keep old behaviour, use ``pip install django-axes[ipware]``
in your install script or use ``django-axes[ipware]``
in your requirements file(s) instead of plain ``django-axes``.
The new ``django-axes`` package does not include ``django-ipware`` by default
but does use ``django-ipware`` if it is installed
and no callables for IP address resolution are configured
with the ``settings.AXES_CLIENT_IP_CALLABLE`` configuration flag.
``django-ipware`` related flags have changed names.
The old flags have been deprecated and will be removed in the future.
To keep old behaviour, rename them in your settings file:
- ``settings.AXES_PROXY_ORDER`` is now ``settings.AXES_IPWARE_PROXY_ORDER``,
- ``settings.AXES_PROXY_COUNT`` is now ``settings.AXES_IPWARE_PROXY_COUNT``,
- ``settings.AXES_PROXY_TRUSTED_IPS`` is now ``settings.AXES_IPWARE_PROXY_TRUSTED_IPS``, and
- ``settings.AXES_META_PRECEDENCE_ORDER`` is now ``settings.AXES_IPWARE_META_PRECEDENCE_ORDER``.
``settings.AXES_LOCKOUT_PARAMETERS`` configuration flag has been added which supersedes the following configuration keys:
#. No configuration for failure tracking in the following items (default behaviour).
#. ``settings.AXES_ONLY_USER_FAILURES``,
#. ``settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``,
#. ``settings.AXES_LOCK_OUT_BY_USER_OR_IP``, and
#. ``settings.AXES_USE_USER_AGENT``.
To keep old behaviour with the new flag, configure the following:
#. If you did not use any flags, use ``settings.AXES_LOCKOUT_PARAMETERS = ["ip_address"]``,
#. If you used ``settings.AXES_ONLY_USER_FAILURES``, use ``settings.AXES_LOCKOUT_PARAMETERS = ["username"]``,
#. If you used ``settings.AXES_LOCK_OUT_BY_USER_OR_IP``, use ``settings.AXES_LOCKOUT_PARAMETERS = ["username", "ip_address"]``, and
#. If you used ``settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``, use ``settings.AXES_LOCKOUT_PARAMETERS = [["username", "ip_address"]]``,
#. If you used ``settings.AXES_USE_USER_AGENT``, add ``"user_agent"`` to your list(s) of lockout parameters.
#. ``settings.AXES_USE_USER_AGENT`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent"]]``
#. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_ONLY_USER_FAILURES`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["username", "user_agent"]]``
#. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_LOCK_OUT_BY_USER_OR_IP`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent"], "username"]``
#. ``settings.AXES_USE_USER_AGENT`` with ``settings.AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP`` would become ``settings.AXES_LOCKOUT_PARAMETERS = [["ip_address", "user_agent", "username"]]``
#. Other combinations of flags were previously not considered; the flags had precedence over each other as described in the documentation but were less-than-trivial to understand in their previous form. The new form is more explicit and flexible, although it requires more in-depth configuration.
The new lockout parameters define a combined list of attributes to consider when tracking failed authentication attempts.
They can be any combination of ``username``, ``ip_address`` or ``user_agent`` in a list of strings or list of lists of strings.
The attributes defined in the lists are combined and saved into the database, cache, or other backend for failed logins.
The semantics of the evaluation are available in the documentation and ``axes.helpers.get_client_parameters`` callable.
``settings.AXES_HTTP_RESPONSE_CODE`` default has been changed from ``403`` (Forbidden) to ``429`` (Too Many Requests).
To keep the old behavior, set ``settings.AXES_HTTP_RESPONSE_CODE = 403`` in your settings.
``axes.handlers.base.AxesBaseHandler.is_admin_site`` has been deprecated due to misleading naming
in favour of better-named ``axes.handlers.base.AxesBaseHandler.is_admin_request``.
The old implementation has been kept for backwards compatibility, but will be removed in the future.
The old implementation checked if a request is NOT made for an admin site if ``settings.AXES_ONLY_ADMIN_SITE`` was set.
The new implementation correctly checks if a request is made for an admin site.
``axes.handlers.cache.AxesCacheHandler`` has been updated to use atomic ``cache.incr`` calls
instead of old ``cache.set`` calls in authentication failure tracking
to enable better parallel backend support for atomic cache backends like Redis and Memcached.
Disabling Axes system checks
----------------------------
@ -122,7 +215,7 @@ other code, preventing the login mechanisms from working due to e.g. exception
being thrown in some part of the code, preventing access attempts being logged
to database with Axes or causing similar problems.
If new attempts or log objects are not being correctly written to the Axes tables,
If new attempts or log objects are not being correctly written to the Axes tables,
it is possible to configure Django ``ATOMIC_REQUESTS`` setting to to ``False``::
ATOMIC_REQUESTS = False

View file

@ -80,7 +80,7 @@ Resetting attempts from command line
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Axes offers a command line interface with
``axes_reset``, ``axes_reset_ip``, and ``axes_reset_username``
``axes_reset``, ``axes_reset_ip``, ``axes_reset_username``, and ``axes_reset_ip_username``
management commands with the Django ``manage.py`` or ``django-admin`` command helpers:
- ``python manage.py axes_reset``
@ -89,6 +89,8 @@ management commands with the Django ``manage.py`` or ``django-admin`` command he
will clear lockouts and records for the given IP addresses.
- ``python manage.py axes_reset_username [username ...]``
will clear lockouts and records for the given usernames.
- ``python manage.py axes_reset_ip_username [ip] [username]``
will clear lockouts and records for the given IP address and username.
- ``python manage.py axes_reset_logs (age)``
will reset (i.e. delete) AccessLog records that are older
than the given age where the default is 30 days.
@ -107,3 +109,24 @@ In your code, you can use the ``axes.utils.reset`` function.
Please note that if you give both ``username`` and ``ip`` arguments to ``reset``
that attempts that have both the set IP and username are reset.
The effective behaviour of ``reset`` is to ``and`` the terms instead of ``or`` ing them.
Data privacy and GDPR
---------------------
Most European countries have quite strict laws regarding data protection and privacy. It's highly recommended and good
practice to treat your sensitive user data with care. The general rule here is that you shouldn't store what you don't need.
When dealing with brute-force protection, the IP address and the username (often the email address) are most crucial.
Given that you can perfectly use `django-axes` without locking the user out by IP but by username, it does make sense to
avoid storing the IP address at all. You can not lose what you don't have.
You can adjust the AXES settings as follows::
# Block by Username only (i.e.: Same user different IP is still blocked, but different user same IP is not)
AXES_LOCKOUT_PARAMETERS = ["username"]
# Disable logging the IP-Address of failed login attempts by returning None for attempts to get the IP
# Ignore assigning a lambda function to a variable for brevity
AXES_CLIENT_IP_CALLABLE = lambda x: None # noqa: E731

View file

@ -14,123 +14,77 @@ Configuring project settings
The following ``settings.py`` options are available for customizing Axes behaviour.
* ``AXES_ENABLED``: Enable or disable Axes plugin functionality,
for example in test runner setup. Default: ``True``
* ``AXES_FAILURE_LIMIT``: The integer number of login attempts allowed before a
record is created for the failed logins. This can also be a callable
or a dotted path to callable that returns an integer and all of the following are valid:
``AXES_FAILURE_LIMIT = 42``,
``AXES_FAILURE_LIMIT = lambda *args: 42``, and
``AXES_FAILURE_LIMIT = 'project.app.get_login_failure_limit'``.
Default: ``3``
* ``AXES_LOCK_OUT_AT_FAILURE``: After the number of allowed login attempts
are exceeded, should we lock out this IP (and optional user agent)?
Default: ``True``
* ``AXES_COOLOFF_TIME``: If set, defines a period of inactivity after which
old failed login attempts will be cleared.
Can be set to a Python timedelta object, an integer, a float, a callable,
or a string path to a callable which takes no arguments.
If an integer or float, will be interpreted as a number of hours:
``AXES_COOLOFF_TIME = 2`` 2 hours
``AXES_COOLOFF_TIME = 2.0`` 2 hours, 120 minutes
``AXES_COOLOFF_TIME = 1.7`` 1.7 hours, 102 minutes, 6120 seconds
Default: ``None``
* ``AXES_ONLY_ADMIN_SITE``: If ``True``, lock is only enabled for admin site.
Admin site is determined by checking request path against the path of ``"admin:index"`` view.
If admin urls are not registered in current urlconf, all requests will not be locked.
Default: ``False``
* ``AXES_ONLY_USER_FAILURES`` : If ``True``, only lock based on username,
and never lock based on IP if attempts exceed the limit.
Otherwise utilize the existing IP and user locking logic.
Default: ``False``
* ``AXES_ENABLE_ADMIN``: If ``True``, admin views for access attempts and
logins are shown in Django admin interface.
Default: ``True``
* ``AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``: If ``True``, prevent login
from IP under a particular username if the attempt limit has been exceeded,
otherwise lock out based on IP.
Default: ``False``
* ``AXES_LOCK_OUT_BY_USER_OR_IP``: If ``True``, prevent login
from if the attempt limit has been exceeded for IP or username.
Default: ``False``
* ``AXES_USE_USER_AGENT``: If ``True``, lock out and log based on the IP address
and the user agent. This means requests from different user agents but from
the same IP are treated differently. This settings has no effect if the
``AXES_ONLY_USER_FAILURES`` setting is active.
Default: ``False``
* ``AXES_HANDLER``: The path to the handler class to use.
If set, overrides the default signal handler backend.
Default: ``'axes.handlers.database.AxesDatabaseHandler'``
* ``AXES_CACHE``: The name of the cache for Axes to use.
Default: ``'default'``
* ``AXES_LOCKOUT_TEMPLATE``: If set, specifies a template to render when a
user is locked out. Template receives ``cooloff_timedelta``, ``cooloff_time``, ``username`` and ``failure_limit`` as
context variables.
Default: ``None``
* ``AXES_LOCKOUT_URL``: If set, specifies a URL to redirect to on lockout. If both
``AXES_LOCKOUT_TEMPLATE`` and ``AXES_LOCKOUT_URL`` are set, the template will be used.
Default: ``None``
* ``AXES_VERBOSE``: If ``True``, you'll see slightly more logging for Axes.
Default: ``True``
* ``AXES_USERNAME_FORM_FIELD``: the name of the form field that contains your users usernames.
Default: ``username``
* ``AXES_USERNAME_CALLABLE``: A callable or a string path to callable that takes
two arguments for user lookups: ``def get_username(request: HttpRequest, credentials: dict) -> str: ...``.
This can be any callable such as ``AXES_USERNAME_CALLABLE = lambda request, credentials: 'username'``
or a full Python module path to callable such as ``AXES_USERNAME_CALLABLE = 'example.get_username``.
The ``request`` is a HttpRequest like object and the ``credentials`` is a dictionary like object.
``credentials`` are the ones that were passed to Django ``authenticate()`` in the login flow.
If no function is supplied, Axes fetches the username from the ``credentials`` or ``request.POST``
dictionaries based on ``AXES_USERNAME_FORM_FIELD``.
* ``AXES_WHITELIST_CALLABLE``: A callable or a string path to callable that takes
two arguments for whitelisting determination and returns True,
if user should be whitelisted:
``def is_whitelisted(request: HttpRequest, credentials: dict) -> bool: ...``.
This can be any callable similarly to ``AXES_USERNAME_CALLABLE``.
Default: ``None``
* ``AXES_LOCKOUT_CALLABLE``: A callable or a string path to callable that takes
two arguments returns a response. For example:
``def generate_lockout_response(request: HttpRequest, credentials: dict) -> HttpResponse: ...``.
This can be any callable similarly to ``AXES_USERNAME_CALLABLE``.
If not callable is defined, then the default implementation in ``axes.helpers.get_lockout_response``
is used for determining the correct lockout response that is sent to the requesting client.
Default: ``None``
* ``AXES_PASSWORD_FORM_FIELD``: the name of the form or credentials field that contains your users password.
Default: ``password``
* ``AXES_SENSITIVE_PARAMETERS``: Configures POST and GET parameter values (in addition to the value of
``AXES_PASSWORD_FORM_FIELD``) to mask in login attempt logging.
Default: ``[]``
* ``AXES_NEVER_LOCKOUT_GET``: If ``True``, Axes will never lock out HTTP GET requests.
Default: ``False``
* ``AXES_NEVER_LOCKOUT_WHITELIST``: If ``True``, users can always login from whitelisted IP addresses.
Default: ``False``
* ``AXES_IP_BLACKLIST``: An iterable of IPs to be blacklisted.
Takes precedence over whitelists. For example: ``AXES_IP_BLACKLIST = ['0.0.0.0']``.
Default: ``None``
* ``AXES_IP_WHITELIST``: An iterable of IPs to be whitelisted.
For example: ``AXES_IP_WHITELIST = ['0.0.0.0']``.
Default: ``None``
* ``AXES_DISABLE_ACCESS_LOG``: If ``True``, disable writing login and logout access logs to database,
so the admin interface will not have user login trail for successful user authentication.
Default: ``False``
* ``AXES_ENABLE_ACCESS_FAILURE_LOG``: If ``True``, enable writing
login failure logs to database, so you will have every user login
trail for unsuccessful user authentication. Default: ``False``
* ``AXES_ACCESS_FAILURE_LOG_PER_USER_LIMIT``: Sets the number of
failures to trail for each user. When the access failure log reach
this number of records, an automatic removal is ran. Default:
``1000``
* ``AXES_RESET_ON_SUCCESS``: If ``True``, a successful login will reset the number of failed logins.
Default: ``False``
* ``AXES_ALLOWED_CORS_ORIGINS``: Configures lockout response CORS headers for XHR requests.
Default: ``*``
* ``AXES_HTTP_RESPONSE_CODE``: Sets the http response code returned when ``AXES_FAILURE_LIMIT`` is
reached.
For example: ``AXES_HTTP_RESPONSE_CODE = 429``
Default: ``403``
* ``AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT``: If ``True``, a failed login attempt during lockout will
reset the cool off period.
Default: ``True``
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable | Default | Explanation |
+======================================================+==============================================+===========================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================+
| AXES_ENABLED | True | Enable or disable Axes plugin functionality, for example in test runner setup |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_FAILURE_LIMIT | 3 | The integer number of login attempts allowed before a record is created for the failed logins. This can also be a callable or a dotted path to callable that returns an integer and all of the following are valid: ``AXES_FAILURE_LIMIT = 42``, ``AXES_FAILURE_LIMIT = lambda *args: 42``, and ``AXES_FAILURE_LIMIT = 'project.app.get_login_failure_limit'``. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCK_OUT_AT_FAILURE | True | After the number of allowed login attempts are exceeded, should we lock out this IP (and optional user agent)? |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_COOLOFF_TIME | None | If set, defines a period of inactivity after which old failed login attempts will be cleared. Can be set to a Python timedelta object, an integer, a float, a callable, or a string path to a callable which takes the request as argument. If an integer or float, will be interpreted as a number of hours: ``AXES_COOLOFF_TIME = 2`` 2 hours, ``AXES_COOLOFF_TIME = 2.0`` 2 hours, 120 minutes, ``AXES_COOLOFF_TIME = 1.7`` 1.7 hours, 102 minutes, 6120 seconds |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ONLY_ADMIN_SITE | False | If ``True``, lock is only enabled for admin site. Admin site is determined by checking request path against the path of ``"admin:index"`` view. If admin urls are not registered in current urlconf, all requests will not be locked. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ONLY_USER_FAILURES | False | DEPRECATED: USE ``AXES_LOCKOUT_PARAMETERS`` INSTEAD. If ``True``, only lock based on username, and never lock based on IP if attempts exceed the limit. Otherwise utilize the existing IP and user locking logic. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ENABLE_ADMIN | True | If ``True``, admin views for access attempts and logins are shown in Django admin interface. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP | False | DEPRECATED: USE ``AXES_LOCKOUT_PARAMETERS`` INSTEAD. If ``True``, prevent login from IP under a particular username if the attempt limit has been exceeded, otherwise lock out based on IP. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCK_OUT_BY_USER_OR_IP | False | DEPRECATED: USE ``AXES_LOCKOUT_PARAMETERS`` INSTEAD. If ``True``, prevent login from if the attempt limit has been exceeded for IP or username. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_USE_USER_AGENT | False | DEPRECATED: USE ``AXES_LOCKOUT_PARAMETERS`` INSTEAD. If ``True``, lock out and log based on the IP address and the user agent. This means requests from different user agents but from the same IP are treated differently. This settings has no effect if the ``AXES_ONLY_USER_FAILURES`` setting is active. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_HANDLER | 'axes.handlers.database.AxesDatabaseHandler' | The path to the handler class to use. If set, overrides the default signal handler backend. Default: ``'axes.handlers.database.AxesDatabaseHandler'`` |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_CACHE | 'default' | The name of the cache for Axes to use. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCKOUT_TEMPLATE | None | If set, specifies a template to render when a user is locked out. Template receives ``cooloff_timedelta``, ``cooloff_time``, ``username`` and ``failure_limit`` as context variables. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCKOUT_URL | None | If set, specifies a URL to redirect to on lockout. If both ``AXES_LOCKOUT_TEMPLATE`` and ``AXES_LOCKOUT_URL`` are set, the template will be used. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_VERBOSE | True | If ``True``, you'll see slightly more logging for Axes. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_USERNAME_FORM_FIELD | 'settings.AUTH_USER_MODEL.USERNAME_FIELD' | The name of the form field that contains your users usernames. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_USERNAME_CALLABLE | None | A callable or a string path to callable that takes two arguments for user lookups: ``def get_username(request: HttpRequest, credentials: dict) -> str: ...``. This can be any callable such as ``AXES_USERNAME_CALLABLE = lambda request, credentials: 'username'`` or a full Python module path to callable such as ``AXES_USERNAME_CALLABLE = 'example.get_username``. The ``request`` is a HttpRequest like object and the ``credentials`` is a dictionary like object. ``credentials`` are the ones that were passed to Django ``authenticate()`` in the login flow. If no function is supplied, Axes fetches the username from the ``credentials`` or ``request.POST`` dictionaries based on ``AXES_USERNAME_FORM_FIELD``. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_WHITELIST_CALLABLE | None | A callable or a string path to callable that takes two arguments for whitelisting determination and returns True, if user should be whitelisted: ``def is_whitelisted(request: HttpRequest, credentials: dict) -> bool: ...``. This can be any callable similarly to ``AXES_USERNAME_CALLABLE``. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCKOUT_CALLABLE | None | A callable or a string path to callable that takes three arguments returns a response. For example: ``def generate_lockout_response(request: HttpRequest, original_response: HttpResponse, credentials: dict) -> HttpResponse: ...``. This can be any callable similarly to ``AXES_USERNAME_CALLABLE``. If not callable is defined, then the default implementation in ``axes.helpers.get_lockout_response`` is used for determining the correct lockout response that is sent to the requesting client. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_CLIENT_IP_CALLABLE | None | A callable or a string path to callable that takes HttpRequest as an argument and returns the resolved IP as a response. For example: ``def get_ip(request: HttpRequest) -> str: ...``. This can be any callable similarly to ``AXES_USERNAME_CALLABLE``. If not callable is defined, then the default implementation in ``axes.helpers.get_client_ip_address`` is used. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_PASSWORD_FORM_FIELD | 'password' | The name of the form or credentials field that contains your users password. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_SENSITIVE_PARAMETERS | ["username", "ip_address"] | Configures POST and GET parameter values (in addition to the value of ``AXES_PASSWORD_FORM_FIELD``) to mask in login attempt logging. Defaults enable privacy-by-design. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_NEVER_LOCKOUT_GET | False | If ``True``, Axes will never lock out HTTP GET requests. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_NEVER_LOCKOUT_WHITELIST | False | If ``True``, users can always login from whitelisted IP addresses. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_IP_BLACKLIST | None | An iterable of IPs to be blacklisted. Takes precedence over whitelists. For example: ``AXES_IP_BLACKLIST = ['0.0.0.0']``. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_IP_WHITELIST | None | An iterable of IPs to be whitelisted. For example: ``AXES_IP_WHITELIST = ['0.0.0.0']``. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_DISABLE_ACCESS_LOG | False | If ``True``, disable writing login and logout access logs to database, so the admin interface will not have user login trail for successful user authentication. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ENABLE_ACCESS_FAILURE_LOG | False | If ``True``, enable writing login failure logs to database, so you will have every user login trail for unsuccessful user authentication. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ACCESS_FAILURE_LOG_PER_USER_LIMIT | 1000 | Sets the number of failures to trail for each user. When the access failure log reach this number of records, an automatic removal is ran. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_RESET_ON_SUCCESS | False | If ``True``, a successful login will reset the number of failed logins. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_ALLOWED_CORS_ORIGINS | "*" | Configures lockout response CORS headers for XHR requests. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_HTTP_RESPONSE_CODE | 429 | Sets the http response code returned when ``AXES_FAILURE_LIMIT`` is reached. For example: ``AXES_HTTP_RESPONSE_CODE = 403`` |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_RESET_COOL_OFF_ON_FAILURE_DURING_LOCKOUT | True | If ``True``, a failed login attempt during lockout will reset the cool off period. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| AXES_LOCKOUT_PARAMETERS | ["ip_address"] | A list of parameters that Axes uses to lock out users. It can also be callable, which takes an http request or AccesAttempt object and credentials and returns a list of parameters. Each parameter can be a string (a single parameter) or a list of strings (a combined parameter). For example, if you configure ``AXES_LOCKOUT_PARAMETERS = ["ip_address", ["username", "user_agent"]]``, axes will block clients by ip and/or username and user agent combination. See :ref:`customizing-lockout-parameters` for more details. |
+------------------------------------------------------+----------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
The configuration option precedences for the access attempt monitoring are:
@ -151,10 +105,12 @@ and uses some conservative configuration parameters by default for security.
If you are using reverse proxies, you will need to configure one or more of the
following settings to suit your set up to correctly resolve client IP addresses:
* ``AXES_PROXY_COUNT``: The number of reverse proxies in front of Django as an integer. Default: ``None``
* ``AXES_META_PRECEDENCE_ORDER``: The names of ``request.META`` attributes as a tuple of strings
* ``AXES_IPWARE_PROXY_COUNT``: The number of reverse proxies in front of Django as an integer. Default: ``None``
* ``AXES_IPWARE_META_PRECEDENCE_ORDER``: The names of ``request.META`` attributes as a tuple of strings
to check to get the client IP address. Check the Django documentation for header naming conventions.
Default: ``IPWARE_META_PRECEDENCE_ORDER`` setting if set, else ``('REMOTE_ADDR', )``
* ``AXES_IPWARE_PROXY_ORDER``: The order in which to evaluate IP addresses from proxy headers when multiple IPs are present
in the header chain. Must be either ``"left-most"`` or ``"right-most"``. **Default:** ``"left-most"``
.. note::
For reverse proxies or e.g. Heroku, you might also want to fetch IP addresses from a HTTP header such as ``X-Forwarded-For``. To configure this, you can fetch IPs through the ``HTTP_X_FORWARDED_FOR`` key from the ``request.META`` property which contains all the HTTP headers in Django:
@ -162,7 +118,7 @@ following settings to suit your set up to correctly resolve client IP addresses:
.. code-block:: python
# refer to the Django request and response objects documentation
AXES_META_PRECEDENCE_ORDER = [
AXES_IPWARE_META_PRECEDENCE_ORDER = [
'HTTP_X_FORWARDED_FOR',
'REMOTE_ADDR',
]
@ -183,6 +139,12 @@ with the ``AXES_HANDLER`` setting in project configuration:
logs attempts to database and creates AccessAttempt and AccessLog records
that persist until removed from the database manually or automatically
after their cool offs expire (checked on each login event).
.. note::
To keep track of concurrent sessions AccessLog stores an hash of ``session_key`` if the session engine is configured.
When no session engine is configured each access is stored with the same dummy value, then a logout will cause each *not-logged-out yet* logs to set a logout time.
Due to how ``django.contrib.auth`` works it is not possible to correctly track the logout of a session in which the user changed its password, since it will create a new session without firing any logout event.
- ``axes.handlers.cache.AxesCacheHandler``
only uses the cache for monitoring attempts and does not persist data
other than in the cache backend; this data can be purged automatically

View file

@ -34,7 +34,7 @@ Here is a more detailed example of sending the necessary signals using
and a custom auth backend at an endpoint that expects JSON
requests. The custom authentication can be swapped out with ``authenticate``
and ``login`` from ``django.contrib.auth``, but beware that those methods take
care of sending the nessary signals for you, and there is no need to duplicate
care of sending the necessary signals for you, and there is no need to duplicate
them as per the example.
``example/forms.py``::
@ -155,7 +155,6 @@ into ``my_namespace-username``:
fine, but Axes does not inject these changes into the authentication flow
for you.
Customizing lockout responses
-----------------------------
@ -167,9 +166,65 @@ An example of usage could be e.g. a custom view for processing lockouts.
from django.http import JsonResponse
def lockout(request, credentials, *args, **kwargs):
def lockout(request, response, credentials, *args, **kwargs):
return JsonResponse({"status": "Locked out due to too many login failures"}, status=403)
``settings.py``::
AXES_LOCKOUT_CALLABLE = "example.views.lockout"
.. _customizing-lockout-parameters:
Customizing lockout parameters
------------------------------
Axes can be configured with ``AXES_LOCKOUT_PARAMETERS`` to lock out users not only by IP address.
``AXES_LOCKOUT_PARAMETERS`` can be a list of strings (which represents a separate lockout parameter) or nested lists of strings (which represents lockout parameters used in combination) or a callable which accepts HttpRequest or AccessAttempt and credentials and returns a list of the same form as described earlier.
Example ``AXES_LOCKOUT_PARAMETERS`` configuration:
``settings.py``::
AXES_LOCKOUT_PARAMETERS = ["ip_address", ["username", "user_agent"]]
This way, axes will lock out users using ip_address or combination of username and user_agent
Example of callable ``AXES_LOCKOUT_PARAMETERS``:
``example/utils.py``::
from django.http import HttpRequest
def get_lockout_parameters(request_or_attempt, credentials):
if isinstance(request_or_attempt, HttpRequest):
is_localhost = request.META.get("REMOTE_ADDR") == "127.0.0.1"
else:
is_localhost = request_or_attempt.ip_address == "127.0.0.1"
if is_localhost:
return ["username"]
return ["ip_address", "username"]
``settings.py``::
AXES_LOCKOUT_PARAMETERS = "example.utils.get_lockout_parameters"
This way, if client ip_address is localhost, axes will lockout client only by username. In other case, axes will lockout client by username or ip_address.
Customizing client ip address lookups
-------------------------------------
Axes can be configured with ``AXES_CLIENT_IP_CALLABLE`` to use custom client ip address lookup logic.
``example/utils.py``::
def get_client_ip(request):
return request.META.get("REMOTE_ADDR")
``settings.py``::
AXES_CLIENT_IP_CALLABLE = "example.utils.get_client_ip"

View file

@ -20,6 +20,7 @@ Django Allauth |check|
Django Simple Captcha |check|
Django OAuth Toolkit |check|
Django Reversion |check|
Django Auth LDAP |check|
======================= ============= ============ ============ ==============
.. |check| unicode:: U+2713

View file

@ -56,7 +56,7 @@ are not blocked, and allows the requests to go through if the check passes.
If the authentication attempt matches a lockout rule, e.g. it is from a
blacklisted IP or exceeds the maximum configured authentication attempts,
it is blocked by raising the ``PermissionDenied`` excepton in the backend.
it is blocked by raising the ``PermissionDenied`` exception in the backend.
Axes monitors logins with the ``user_login_failed`` signal receiver
and records authentication failures from both the ``AxesBackend`` and

3
docs/9_contributing.rst Normal file
View file

@ -0,0 +1,3 @@
.. _contributing:
.. include:: ../CONTRIBUTING.rst

View file

@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://sphinx-doc.org/)
endif
# Internal variables.

9
docs/_static/css/custom_theme.css vendored Normal file
View file

@ -0,0 +1,9 @@
@import url("theme.css");
.wy-nav-content {
max-width: none;
}
.wy-table-responsive table td, .wy-table-responsive table th {
white-space: inherit;
}

View file

@ -3,11 +3,11 @@ Sphinx documentation generator configuration.
More information on the configuration options is available at:
http://www.sphinx-doc.org/en/master/usage/configuration.html
https://www.sphinx-doc.org/en/master/usage/configuration.html
"""
import sphinx_rtd_theme
from pkg_resources import get_distribution
# import sphinx_rtd_theme
from importlib.metadata import version as get_version
import django
from django.conf import settings
@ -25,7 +25,7 @@ description = ("Keep track of failed login attempts in Django-powered sites.",)
# Add any Sphinx extension module names here, as strings.
# They can be extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ["sphinx.ext.autodoc"]
extensions = ["sphinx_rtd_theme","sphinx.ext.autodoc"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
@ -43,7 +43,7 @@ copyright = "2016, Jazzband"
author = "Jazzband"
# The full version, including alpha/beta/rc tags.
release = get_distribution("django-axes").version
release = get_version("django-axes")
# The short X.Y version.
version = ".".join(release.split(".")[:2])
@ -71,8 +71,10 @@ todo_include_todos = False
# a list of builtin themes.
html_theme = "sphinx_rtd_theme"
html_style = "css/custom_theme.css"
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,

View file

@ -18,7 +18,7 @@ Contents
6_integration
7_architecture
8_reference
9_development
9_contributing
10_changelog

View file

@ -1,5 +1,5 @@
[mypy]
python_version = 3.6
python_version = 3.14
ignore_missing_imports = True
[mypy-axes.migrations.*]

View file

@ -10,32 +10,35 @@ DJANGO_SETTINGS_MODULE = "tests.settings"
legacy_tox_ini = """
[tox]
envlist =
py{37,38,39,310,py38}-dj32
py{38,39,310,py38}-dj40
py{38,39,310,py38}-djmain
py310-djqa
py{310,311,312}-dj42
py{310,311,312,313}-dj52
py{312,313,314}-dj60
py314-djmain
py314-djqa
[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39
3.10: py310
pypy-3.8: pypy38
3.11: py311
3.12: py312
3.13: py313
3.14: py314
[gh-actions:env]
DJANGO =
3.2: dj32
4.0: dj40
4.2: dj42
5.2: dj52
6.0: dj60
main: djmain
qa: djqa
# Normal test environment runs pytest which orchestrates other tools
[testenv]
deps =
-r requirements-test.txt
dj32: django>=3.2,<3.3
dj40: django>=4.0,<4.1
-r requirements.txt
dj42: django>=4.2,<4.3
dj52: django>=5.2,<5.3
dj60: django>=6.0,<6.1
djmain: https://github.com/django/django/archive/main.tar.gz
usedevelop = true
commands = pytest
@ -48,10 +51,11 @@ ignore_errors =
djmain: True
# QA runs type checks, linting, and code formatting checks
[testenv:py310-djqa]
deps = -r requirements-qa.txt
[testenv:py314-djqa]
stoponfail = false
deps = -r requirements.txt
commands =
mypy axes
prospector
black -t py38 --check --diff axes
prospector axes
black --check --diff axes
"""

View file

@ -1,4 +0,0 @@
black==22.3.0
mypy==0.960
prospector==1.7.7
types-pkg_resources # Type stub

View file

@ -1,6 +0,0 @@
-e .
coverage==6.4
pytest==7.1.2
pytest-cov==3.0.0
pytest-django==4.5.2
pytest-subtests==0.8.0

View file

@ -1,5 +1,12 @@
-e .
-r requirements-qa.txt
-r requirements-test.txt
sphinx_rtd_theme==1.0.0
tox==3.25.0
black==26.3.1
coverage==7.13.4
django-ipware>=3
mypy==1.19.1
prospector==1.18.0
pytest-cov==7.0.0
pytest-django==4.12.0
pytest-subtests==0.15.0
pytest==9.0.2
sphinx_rtd_theme==3.1.0
tox==4.49.1

View file

@ -35,8 +35,14 @@ setup(
package_dir={"axes": "axes"},
use_scm_version=True,
setup_requires=["setuptools_scm"],
python_requires=">=3.7",
install_requires=["django>=3.2", "django-ipware>=3", "setuptools"],
python_requires=">=3.10",
install_requires=[
"django>=4.2",
"asgiref>=3.6.0",
],
extras_require={
"ipware": "django-ipware>=3",
},
include_package_data=True,
packages=find_packages(exclude=["tests"]),
classifiers=[
@ -44,20 +50,21 @@ setup(
"Environment :: Web Environment",
"Environment :: Plugins",
"Framework :: Django",
"Framework :: Django :: 3.2",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.2",
"Framework :: Django :: 6.0",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: Log Analysis",
"Topic :: Security",
"Topic :: System :: Logging",

View file

@ -3,6 +3,7 @@ from string import ascii_letters, digits
from time import sleep
from django.contrib.auth import get_user_model
from django.contrib.auth.base_user import AbstractBaseUser
from django.http import HttpRequest
from django.test import TestCase
from django.urls import reverse
@ -48,7 +49,7 @@ class AxesTestCase(TestCase):
STATUS_SUCCESS = 200
ALLOWED = 302
BLOCKED = 403
BLOCKED = 429
def setUp(self):
"""

View file

@ -20,7 +20,7 @@ MIDDLEWARE = [
]
AUTHENTICATION_BACKENDS = [
"axes.backends.AxesBackend",
"axes.backends.AxesStandaloneBackend",
"django.contrib.auth.backends.ModelBackend",
]

View file

@ -1,7 +1,7 @@
from unittest.mock import patch
from django.http import HttpRequest
from django.test import override_settings
from django.test import override_settings, RequestFactory
from django.utils.timezone import now
from axes.attempts import get_cool_off_threshold
@ -15,12 +15,13 @@ class GetCoolOffThresholdTestCase(AxesTestCase):
def test_get_cool_off_threshold(self):
timestamp = now()
request = RequestFactory().post("/")
with patch("axes.attempts.now", return_value=timestamp):
attempt_time = timestamp
threshold_now = get_cool_off_threshold(attempt_time)
request.axes_attempt_time = timestamp
threshold_now = get_cool_off_threshold(request)
attempt_time = None
threshold_none = get_cool_off_threshold(attempt_time)
request.axes_attempt_time = None
threshold_none = get_cool_off_threshold(request)
self.assertEqual(threshold_now, threshold_none)
@ -82,74 +83,74 @@ class ResetResponseTestCase(AxesTestCase):
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_reset_user_failures(self):
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 5)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_reset_ip_user_failures(self):
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 5)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_reset_username_user_failures(self):
self.request.GET["username"] = self.USERNAME_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_reset_ip_username_user_failures(self):
self.request.GET["username"] = self.USERNAME_1
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_reset_user_or_ip(self):
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 5)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_reset_ip_user_or_ip(self):
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_reset_username_user_or_ip(self):
self.request.GET["username"] = self.USERNAME_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_reset_ip_username_user_or_ip(self):
self.request.GET["username"] = self.USERNAME_1
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 2)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_reset_user_and_ip(self):
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 5)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_reset_ip_user_and_ip(self):
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_reset_username_user_and_ip(self):
self.request.GET["username"] = self.USERNAME_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_AND=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_reset_ip_username_user_and_ip(self):
self.request.GET["username"] = self.USERNAME_1
self.request.META["REMOTE_ADDR"] = self.IP_1
reset_request(self.request)
self.assertEqual(AccessAttempt.objects.count(), 3)
self.assertEqual(AccessAttempt.objects.count(), 4)

View file

@ -1,7 +1,7 @@
from django.core.checks import run_checks, Warning # pylint: disable=redefined-builtin
from django.test import override_settings, modify_settings
from axes.backends import AxesBackend
from axes.backends import AxesStandaloneBackend
from axes.checks import Messages, Hints, Codes
from tests.base import AxesTestCase
@ -58,12 +58,14 @@ class MiddlewareCheckTestCase(AxesTestCase):
self.assertEqual(warnings, [warning])
class AxesSpecializedBackend(AxesBackend):
class AxesSpecializedBackend(AxesStandaloneBackend):
pass
class BackendCheckTestCase(AxesTestCase):
@modify_settings(AUTHENTICATION_BACKENDS={"remove": ["axes.backends.AxesBackend"]})
@modify_settings(
AUTHENTICATION_BACKENDS={"remove": ["axes.backends.AxesStandaloneBackend"]}
)
def test_backend_missing(self):
warnings = run_checks()
warning = Warning(
@ -108,3 +110,43 @@ class DeprecatedSettingsTestCase(AxesTestCase):
def test_deprecated_success_access_log_flag(self):
warnings = run_checks()
self.assertEqual(warnings, [self.disable_success_access_log_warning])
class ConfCheckTestCase(AxesTestCase):
@override_settings(AXES_USERNAME_CALLABLE="module.not_defined")
def test_invalid_import_path(self):
warnings = run_checks()
warning = Warning(
msg=Messages.CALLABLE_INVALID.format(
callable_setting="AXES_USERNAME_CALLABLE"
),
hint=Hints.CALLABLE_INVALID,
id=Codes.CALLABLE_INVALID,
)
self.assertEqual(warnings, [warning])
@override_settings(AXES_COOLOFF_TIME=lambda: 1)
def test_valid_callable(self):
warnings = run_checks()
self.assertEqual(warnings, [])
def test_missing_settings_no_error(self):
warnings = run_checks()
self.assertEqual(warnings, [])
class LockoutParametersCheckTestCase(AxesTestCase):
@override_settings(AXES_LOCKOUT_PARAMETERS=["ip_address", "username"])
def test_valid_configuration(self):
warnings = run_checks()
self.assertEqual(warnings, [])
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "user_agent"])
def test_invalid_configuration(self):
warnings = run_checks()
warning = Warning(
msg=Messages.LOCKOUT_PARAMETERS_INVALID,
hint=Hints.LOCKOUT_PARAMETERS_INVALID,
id=Codes.LOCKOUT_PARAMETERS_INVALID,
)
self.assertEqual(warnings, [warning])

45
tests/test_conf.py Normal file
View file

@ -0,0 +1,45 @@
from django.test import TestCase
from django.utils.functional import SimpleLazyObject
class ConfTestCase(TestCase):
def test_axes_username_form_field_uses_lazy_evaluation(self):
"""
Test that AXES_USERNAME_FORM_FIELD uses SimpleLazyObject for lazy evaluation.
This prevents circular import issues with custom user models (issue #1280).
"""
from axes.conf import settings
# Verify that AXES_USERNAME_FORM_FIELD is a SimpleLazyObject if not overridden
# This is only the case when the setting is not explicitly defined
username_field = settings.AXES_USERNAME_FORM_FIELD
# The actual type depends on whether AXES_USERNAME_FORM_FIELD was overridden
# If it's using the default, it should be a SimpleLazyObject
# If overridden in settings, it could be a plain string
# Either way, it should be usable as a string
# Force evaluation and verify it works
username_field_str = str(username_field)
# Should get the default USERNAME_FIELD from the user model
# For the test suite, this is "username"
self.assertIsInstance(username_field_str, str)
self.assertTrue(len(username_field_str) > 0)
def test_axes_username_form_field_evaluates_correctly(self):
"""
Test that when AXES_USERNAME_FORM_FIELD is accessed, it correctly
resolves to the user model's USERNAME_FIELD.
"""
from django.contrib.auth import get_user_model
from axes.conf import settings
# Get the expected value
expected_username_field = get_user_model().USERNAME_FIELD
# Get the actual value from axes settings
actual_username_field = str(settings.AXES_USERNAME_FORM_FIELD)
# They should match
self.assertEqual(actual_username_field, expected_username_field)

View file

@ -8,7 +8,7 @@ from tests.base import AxesTestCase
class DecoratorTestCase(AxesTestCase):
SUCCESS_RESPONSE = HttpResponse(status=200, content="Dispatched")
LOCKOUT_RESPONSE = HttpResponse(status=403, content="Locked out")
LOCKOUT_RESPONSE = HttpResponse(status=429, content="Locked out")
def setUp(self):
self.request = MagicMock()

View file

@ -1,18 +1,20 @@
from platform import python_implementation
from unittest.mock import MagicMock, patch
from datetime import datetime, timezone as dt_timezone
from django.test import override_settings
from django.utils import timezone
from axes.handlers.database import AxesDatabaseHandler
from axes.models import AccessAttempt, AccessLog, AccessFailureLog, AccessAttemptExpiration
from pytest import mark
from django.core.cache import cache
from django.test import override_settings
from django.urls import reverse
from django.utils import timezone
from django.utils.timezone import timedelta
from axes.conf import settings
from axes.handlers.proxy import AxesProxyHandler
from axes.helpers import get_client_str
from axes.models import AccessAttempt, AccessLog, AccessFailureLog
from tests.base import AxesTestCase
@ -55,14 +57,36 @@ class AxesHandlerTestCase(AxesTestCase):
for setting_value, url, expected in tests:
with override_settings(AXES_ONLY_ADMIN_SITE=setting_value):
request.path = url
self.assertEqual(AxesProxyHandler().is_admin_site(request), expected)
with self.assertWarns(DeprecationWarning):
self.assertEqual(AxesProxyHandler().is_admin_site(request), expected)
def test_is_admin_request(self):
request = MagicMock()
tests = ( # (URL, Expected)
("/test/", False),
(reverse("admin:index"), True),
)
for url, expected in tests:
request.path = url
self.assertEqual(AxesProxyHandler().is_admin_request(request), expected)
@override_settings(ROOT_URLCONF="tests.urls_empty")
@override_settings(AXES_ONLY_ADMIN_SITE=True)
def test_is_admin_site_no_admin_site(self):
request = MagicMock()
request.path = "/admin/"
self.assertTrue(AxesProxyHandler().is_admin_site(self.request))
with self.assertWarns(DeprecationWarning):
self.assertTrue(AxesProxyHandler().is_admin_site(self.request))
@override_settings(ROOT_URLCONF="tests.urls_empty")
def test_is_admin_request_no_admin_site(self):
request = MagicMock()
request.path = "/admin/"
self.assertFalse(AxesProxyHandler().is_admin_request(self.request))
def test_is_admin_request_no_path(self):
self.assertFalse(AxesProxyHandler().is_admin_request(self.request))
class AxesProxyHandlerTestCase(AxesTestCase):
@ -214,11 +238,6 @@ class ResetAttemptsTestCase(AxesHandlerBaseTestCase):
AXES_RESET_ON_SUCCESS=True,
AXES_ENABLE_ACCESS_FAILURE_LOG=True,
)
@mark.xfail(
python_implementation() == "PyPy",
reason="PyPy implementation is flaky for this test",
strict=False,
)
class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
def test_handler_reset_attempts(self):
self.create_attempt()
@ -261,7 +280,10 @@ class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
_more = 10
for i in range(settings.AXES_ACCESS_FAILURE_LOG_PER_USER_LIMIT + _more):
self.create_failure_log()
self.assertEqual(_more, AxesProxyHandler.remove_out_of_limit_failure_logs(username=self.username))
self.assertEqual(
_more,
AxesProxyHandler.remove_out_of_limit_failure_logs(username=self.username),
)
@override_settings(AXES_RESET_ON_SUCCESS=True)
def test_handler(self):
@ -296,7 +318,7 @@ class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
def test_whitelist(self, log):
self.check_whitelist(log)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
@patch("axes.handlers.database.log")
def test_user_login_failed_only_user_failures_with_none_username(self, log):
credentials = {"username": None, "password": "test"}
@ -305,7 +327,7 @@ class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
attempt = AccessAttempt.objects.all()
self.assertEqual(0, AccessAttempt.objects.count())
log.warning.assert_called_with(
"AXES: Username is None and AXES_ONLY_USER_FAILURES is enabled, new record will NOT be created."
"AXES: Username is None and username is the only one lockout parameter, new record will NOT be created."
)
def test_user_login_failed_with_none_username(self):
@ -318,22 +340,37 @@ class AxesDatabaseHandlerTestCase(AxesHandlerBaseTestCase):
def test_user_login_failed_multiple_username(self):
configurations = (
(2, 1, {}, ["admin", "admin1"]),
(2, 1, {"AXES_USE_USER_AGENT": True}, ["admin", "admin1"]),
(2, 1, {"AXES_ONLY_USER_FAILURES": True}, ["admin", "admin1"]),
(
2,
1,
{"AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP": True},
{"AXES_LOCKOUT_PARAMETERS": [["ip_address", "user_agent"]]},
["admin", "admin1"],
),
(2, 1, {"AXES_LOCKOUT_PARAMETERS": ["username"]}, ["admin", "admin1"]),
(
2,
1,
{"AXES_LOCKOUT_PARAMETERS": [["username", "ip_address"]]},
["admin", "admin1"],
),
(
1,
2,
{"AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP": True},
{"AXES_LOCKOUT_PARAMETERS": [["username", "ip_address"]]},
["admin", "admin"],
),
(1, 2, {"AXES_LOCK_OUT_BY_USER_OR_IP": True}, ["admin", "admin"]),
(2, 1, {"AXES_LOCK_OUT_BY_USER_OR_IP": True}, ["admin", "admin1"]),
(
1,
2,
{"AXES_LOCKOUT_PARAMETERS": ["username", "ip_address"]},
["admin", "admin"],
),
(
2,
1,
{"AXES_LOCKOUT_PARAMETERS": ["username", "ip_address"]},
["admin", "admin1"],
),
)
for (
@ -400,7 +437,7 @@ class ResetAttemptsCacheHandlerTestCase(AxesHandlerBaseTestCase):
with self.assertRaises(NotImplementedError):
AxesProxyHandler.reset_attempts()
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_handler_reset_attempts_username(self):
self.set_up_login_attempts()
self.assertEqual(
@ -436,7 +473,7 @@ class ResetAttemptsCacheHandlerTestCase(AxesHandlerBaseTestCase):
self.check_failures(0, ip_address=self.IP_1)
self.check_failures(2, ip_address=self.IP_2)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_handler_reset_attempts_ip_and_username(self):
self.set_up_login_attempts()
self.check_failures(1, username=self.USERNAME_1, ip_address=self.IP_1)
@ -482,7 +519,7 @@ class AxesCacheHandlerTestCase(AxesHandlerBaseTestCase):
def test_whitelist(self, log):
self.check_whitelist(log)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
@patch.object(cache, "set")
@patch("axes.handlers.cache.log")
def test_user_login_failed_only_user_failures_with_none_username(
@ -493,15 +530,15 @@ class AxesCacheHandlerTestCase(AxesHandlerBaseTestCase):
AxesProxyHandler.user_login_failed(sender, credentials, self.request)
self.assertFalse(cache_set.called)
log.warning.assert_called_with(
"AXES: Username is None and AXES_ONLY_USER_FAILURES is enabled, new record will NOT be created."
"AXES: Username is None and username is the only one lockout parameter, new record will NOT be created."
)
@patch.object(cache, "set")
def test_user_login_failed_with_none_username(self, cache_set):
@patch.object(cache, "add")
def test_user_login_failed_with_none_username(self, cache_add):
credentials = {"username": None, "password": "test"}
sender = MagicMock()
AxesProxyHandler.user_login_failed(sender, credentials, self.request)
self.assertTrue(cache_set.called)
self.assertTrue(cache_add.called)
@override_settings(AXES_HANDLER="axes.handlers.dummy.AxesDummyHandler")
@ -532,3 +569,170 @@ class AxesTestHandlerTestCase(AxesHandlerBaseTestCase):
def test_handler_get_failures(self):
self.assertEqual(0, AxesProxyHandler.get_failures(self.request, {}))
@override_settings(AXES_HANDLER="axes.handlers.database.AxesDatabaseHandler", AXES_COOLOFF_TIME=timezone.timedelta(seconds=10))
class AxesDatabaseHandlerExpirationFlagTestCase(AxesTestCase):
def setUp(self):
super().setUp()
self.handler = AxesDatabaseHandler()
self.mock_request = MagicMock()
self.mock_credentials = None
@override_settings(AXES_USE_ATTEMPT_EXPIRATION=True)
@patch("axes.handlers.database.log")
@patch("axes.models.AccessAttempt.objects.filter")
@patch("django.utils.timezone.now")
def test_clean_expired_user_attempts_expiration_true(self, mock_now, mock_filter, mock_log):
mock_now.return_value = datetime(2025, 1, 1, tzinfo=dt_timezone.utc)
mock_qs = MagicMock()
mock_filter.return_value = mock_qs
mock_qs.delete.return_value = (3, None)
count = self.handler.clean_expired_user_attempts(request=None, credentials=None)
mock_filter.assert_called_once_with(expiration__expires_at__lte=mock_now.return_value)
mock_qs.delete.assert_called_once()
mock_log.info.assert_called_with(
"AXES: Cleaned up %s expired access attempts from database that expiry were older than %s",
3,
mock_now.return_value,
)
self.assertEqual(count, 3)
@override_settings(AXES_USE_ATTEMPT_EXPIRATION=True)
@patch("axes.handlers.database.log")
def test_clean_expired_user_attempts_expiration_true_with_complete_deletion(self, mock_log):
AccessAttempt.objects.all().delete()
dummy_attempt = AccessAttempt.objects.create(
username="test_user",
ip_address="192.168.1.1",
failures_since_start=1,
user_agent="test_agent",
)
dummy_attempt.expiration = AccessAttemptExpiration.objects.create(
access_attempt=dummy_attempt,
expires_at=timezone.now() - timezone.timedelta(days=1) # Set to expire in the past
)
count = self.handler.clean_expired_user_attempts(request=None, credentials=None)
mock_log.info.assert_called_once()
# comparing count=2, as one is the dummy attempt and one is the expiration
self.assertEqual(count, 2)
self.assertEqual(
AccessAttempt.objects.count(), 0
)
self.assertEqual(
AccessAttemptExpiration.objects.count(), 0
)
@override_settings(AXES_USE_ATTEMPT_EXPIRATION=True)
@patch("axes.handlers.database.log")
def test_clean_expired_user_attempts_expiration_true_with_partial_deletion(self, mock_log):
attempt_not_expired = AccessAttempt.objects.create(
username="test_user",
ip_address="192.168.1.1",
failures_since_start=1,
user_agent="test_agent",
)
attempt_not_expired.expiration = AccessAttemptExpiration.objects.create(
access_attempt=attempt_not_expired,
expires_at=timezone.now() + timezone.timedelta(days=1) # Set to expire in the future
)
attempt_expired = AccessAttempt.objects.create(
username="test_user_2",
ip_address="192.168.1.2",
failures_since_start=1,
user_agent="test_agent",
)
attempt_expired.expiration = AccessAttemptExpiration.objects.create(
access_attempt=attempt_expired,
expires_at=timezone.now() - timezone.timedelta(days=1) # Set to expire in the past
)
access_attempt_count = AccessAttempt.objects.count()
access_attempt_expiration_count = AccessAttemptExpiration.objects.count()
count = self.handler.clean_expired_user_attempts(request=None, credentials=None)
mock_log.info.assert_called_once()
# comparing count=2, as one is the dummy attempt and one is the expiration
self.assertEqual(count, 2)
self.assertEqual(
AccessAttempt.objects.count(), access_attempt_count - 1
)
self.assertEqual(
AccessAttemptExpiration.objects.count(), access_attempt_expiration_count - 1
)
@override_settings(AXES_USE_ATTEMPT_EXPIRATION=True)
@patch("axes.handlers.database.log")
def test_clean_expired_user_attempts_expiration_true_with_no_deletion(self, mock_log):
attempt_not_expired_1 = AccessAttempt.objects.create(
username="test_user",
ip_address="192.168.1.1",
failures_since_start=1,
user_agent="test_agent",
)
attempt_not_expired_1.expiration = AccessAttemptExpiration.objects.create(
access_attempt=attempt_not_expired_1,
expires_at=timezone.now() + timezone.timedelta(days=1) # Set to expire in the future
)
attempt_not_expired_2 = AccessAttempt.objects.create(
username="test_user_2",
ip_address="192.168.1.2",
failures_since_start=1,
user_agent="test_agent",
)
attempt_not_expired_2.expiration = AccessAttemptExpiration.objects.create(
access_attempt=attempt_not_expired_2,
expires_at=timezone.now() + timezone.timedelta(days=2) # Set to expire in the future
)
access_attempt_count = AccessAttempt.objects.count()
access_attempt_expiration_count = AccessAttemptExpiration.objects.count()
count = self.handler.clean_expired_user_attempts(request=None, credentials=None)
mock_log.info.assert_called_once()
# comparing count=2, as one is the dummy attempt and one is the expiration
self.assertEqual(count, 0)
self.assertEqual(
AccessAttempt.objects.count(), access_attempt_count
)
self.assertEqual(
AccessAttemptExpiration.objects.count(), access_attempt_expiration_count
)
@override_settings(AXES_USE_ATTEMPT_EXPIRATION=False)
@patch("axes.handlers.database.log")
@patch("axes.handlers.database.get_cool_off_threshold")
@patch("axes.models.AccessAttempt.objects.filter")
def test_clean_expired_user_attempts_expiration_false(self, mock_filter, mock_get_threshold, mock_log):
mock_get_threshold.return_value = "fake-threshold"
mock_qs = MagicMock()
mock_filter.return_value = mock_qs
mock_qs.delete.return_value = (2, None)
count = self.handler.clean_expired_user_attempts(request=self.mock_request, credentials=None)
mock_filter.assert_called_once_with(attempt_time__lte="fake-threshold")
mock_qs.delete.assert_called_once()
mock_log.info.assert_called_with(
"AXES: Cleaned up %s expired access attempts from database that were older than %s",
2,
"fake-threshold",
)
self.assertEqual(count, 2)
@override_settings(AXES_COOLOFF_TIME=None)
@patch("axes.handlers.database.log")
def test_clean_expired_user_attempts_no_cooloff(self, mock_log):
count = self.handler.clean_expired_user_attempts(request=None, credentials=None)
mock_log.debug.assert_called_with(
"AXES: Skipping clean for expired access attempts because no AXES_COOLOFF_TIME is configured"
)
self.assertEqual(count, 0)

View file

@ -3,16 +3,18 @@ from hashlib import sha256
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.http import JsonResponse, HttpResponseRedirect, HttpResponse, HttpRequest
from django.test import override_settings, RequestFactory
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
from django.test import RequestFactory, override_settings
from axes.apps import AppConfig
from axes.helpers import (
cleanse_parameters,
get_cache_timeout,
get_client_cache_keys,
get_client_ip_address,
get_client_parameters,
get_client_str,
get_client_username,
get_client_cache_key,
get_client_parameters,
get_cool_off,
get_cool_off_iso8601,
get_lockout_response,
@ -23,7 +25,6 @@ from axes.helpers import (
is_ip_address_in_whitelist,
is_user_attempt_whitelisted,
toggleable,
cleanse_parameters,
)
from axes.models import AccessAttempt
from tests.base import AxesTestCase
@ -58,6 +59,38 @@ class CacheTestCase(AxesTestCase):
def test_get_cache_timeout_none(self):
self.assertEqual(get_cache_timeout(), None)
def test_get_increasing_cache_timeout_by_username(self):
user_durations = {
"ben": timedelta(minutes=5),
"jen": timedelta(minutes=10),
}
def _callback(request):
username = request.POST["username"] if request else object()
previous_duration = user_durations.get(username, timedelta())
user_durations[username] = previous_duration + timedelta(minutes=5)
return user_durations[username]
rf = RequestFactory()
ben_req = rf.post("/", data={"username": "ben"})
jen_req = rf.post("/", data={"username": "jen"})
james_req = rf.post("/", data={"username": "james"})
with override_settings(AXES_COOLOFF_TIME=_callback):
with self.subTest("no username"):
self.assertEqual(get_cache_timeout(), 300)
with self.subTest("ben"):
self.assertEqual(get_cache_timeout(ben_req), 600)
self.assertEqual(get_cache_timeout(ben_req), 900)
self.assertEqual(get_cache_timeout(ben_req), 1200)
with self.subTest("jen"):
self.assertEqual(get_cache_timeout(jen_req), 900)
with self.subTest("james"):
self.assertEqual(get_cache_timeout(james_req), 300)
class TimestampTestCase(AxesTestCase):
def test_iso8601(self):
@ -81,6 +114,7 @@ class TimestampTestCase(AxesTestCase):
self.assertEqual(get_cool_off_iso8601(delta), iso_duration)
@override_settings(AXES_SENSITIVE_PARAMETERS=[])
class ClientStringTestCase(AxesTestCase):
@staticmethod
def get_expected_client_str(*args, **kwargs):
@ -149,7 +183,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
@override_settings(AXES_VERBOSE=True)
def test_verbose_user_only_client_details(self):
username = "test@example.com"
@ -166,7 +200,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
@override_settings(AXES_VERBOSE=False)
def test_non_verbose_user_only_client_details(self):
username = "test@example.com"
@ -181,7 +215,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
@override_settings(AXES_VERBOSE=True)
def test_verbose_user_ip_combo_client_details(self):
username = "test@example.com"
@ -198,7 +232,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
@override_settings(AXES_VERBOSE=False)
def test_non_verbose_user_ip_combo_client_details(self):
username = "test@example.com"
@ -213,7 +247,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_USE_USER_AGENT=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"]])
@override_settings(AXES_VERBOSE=True)
def test_verbose_user_agent_client_details(self):
username = "test@example.com"
@ -230,7 +264,7 @@ class ClientStringTestCase(AxesTestCase):
self.assertEqual(expected, actual)
@override_settings(AXES_USE_USER_AGENT=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"]])
@override_settings(AXES_VERBOSE=False)
def test_non_verbose_user_agent_client_details(self):
username = "test@example.com"
@ -268,6 +302,26 @@ class ClientStringTestCase(AxesTestCase):
self.email,
)
@override_settings(AXES_SENSITIVE_PARAMETERS=["username"])
def test_get_client_str_with_sensitive_parameters(self):
username = "test@example.com"
ip_address = "127.0.0.1"
user_agent = "Googlebot/2.1 (+http://www.googlebot.com/bot.html)"
path_info = "/admin/"
expected = self.get_expected_client_str(
"********************",
ip_address,
user_agent,
path_info,
self.request
)
actual = get_client_str(
username, ip_address, user_agent, path_info, self.request
)
self.assertEqual(expected, actual)
def get_dummy_client_str(username, ip_address, user_agent, path_info, request):
return "client string"
@ -279,76 +333,265 @@ def get_dummy_client_str_using_request(
return f"{request.user.email}"
def get_dummy_lockout_parameters(request, credentials=None):
return ["ip_address", ["username", "user_agent"]]
class ClientParametersTestCase(AxesTestCase):
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_get_filter_kwargs_user(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"username": self.username}],
)
@override_settings(
AXES_ONLY_USER_FAILURES=False,
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=False,
AXES_USE_USER_AGENT=False,
)
def test_get_filter_kwargs_ip(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"ip_address": self.ip_address}],
)
@override_settings(
AXES_ONLY_USER_FAILURES=False,
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True,
AXES_USE_USER_AGENT=False,
)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_get_filter_kwargs_user_and_ip(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"username": self.username, "ip_address": self.ip_address}],
)
@override_settings(
AXES_ONLY_USER_FAILURES=False,
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=False,
AXES_LOCK_OUT_BY_USER_OR_IP=True,
AXES_USE_USER_AGENT=False,
)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "user_agent"]])
def test_get_filter_kwargs_user_and_user_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"username": self.username, "user_agent": self.user_agent}],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=["ip_address", ["username", "user_agent"]])
def test_get_filter_kwargs_ip_or_user_and_user_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"ip_address": self.ip_address}, {"username": self.username, "user_agent": self.user_agent}],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"], ["username", "user_agent"]])
def test_get_filter_kwargs_ip_and_user_agent_or_user_and_user_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"ip_address": self.ip_address, "user_agent": self.user_agent}, {"username": self.username, "user_agent": self.user_agent}],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_get_filter_kwargs_user_or_ip(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"username": self.username}, {"ip_address": self.ip_address}],
)
@override_settings(
AXES_ONLY_USER_FAILURES=False,
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=False,
AXES_USE_USER_AGENT=True,
)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address", "user_agent"])
def test_get_filter_kwargs_user_or_ip_or_user_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"username": self.username}, {"ip_address": self.ip_address}, {"user_agent": self.user_agent}],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"]])
def test_get_filter_kwargs_ip_and_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
[{"ip_address": self.ip_address}, {"user_agent": self.user_agent}],
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[{"ip_address": self.ip_address, "user_agent": self.user_agent}],
)
@override_settings(
AXES_ONLY_USER_FAILURES=False,
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True,
AXES_USE_USER_AGENT=True,
AXES_LOCKOUT_PARAMETERS=[["username", "ip_address", "user_agent"]]
)
def test_get_filter_kwargs_user_ip_agent(self):
self.assertEqual(
get_client_parameters(self.username, self.ip_address, self.user_agent),
get_client_parameters(self.username, self.ip_address, self.user_agent, self.request, self.credentials),
[
{"username": self.username, "ip_address": self.ip_address},
{"user_agent": self.user_agent},
{
"username": self.username,
"ip_address": self.ip_address,
"user_agent": self.user_agent,
},
],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=["wrong_param"])
@patch("axes.helpers.log")
def test_get_filter_kwargs_invalid_parameter(self, log):
with self.assertRaises(ValueError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
log.exception.assert_called_with(
(
"wrong_param lockout parameter is not allowed. "
"Allowed lockout parameters: username, ip_address, user_agent"
)
)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "wrong_param"]])
@patch("axes.helpers.log")
def test_get_filter_kwargs_invalid_combined_parameter(self, log):
with self.assertRaises(ValueError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
log.exception.assert_called_with(
(
"wrong_param lockout parameter is not allowed. "
"Allowed lockout parameters: username, ip_address, user_agent"
)
)
@override_settings(AXES_LOCKOUT_PARAMETERS=get_dummy_lockout_parameters)
def test_get_filter_kwargs_callable_lockout_parameters(self):
self.assertEqual(
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
),
[
{
"ip_address": self.ip_address,
},
{
"username": self.username,
"user_agent": self.user_agent,
},
],
)
@override_settings(
AXES_LOCKOUT_PARAMETERS="tests.test_helpers.get_dummy_lockout_parameters"
)
def test_get_filter_kwargs_callable_str_lockout_parameters(self):
self.assertEqual(
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
),
[
{
"ip_address": self.ip_address,
},
{
"username": self.username,
"user_agent": self.user_agent,
},
],
)
@override_settings(
AXES_LOCKOUT_PARAMETERS=lambda request, credentials: ["username"]
)
def test_get_filter_kwargs_callable_lambda_lockout_parameters(self):
self.assertEqual(
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
),
[
{
"username": self.username,
},
],
)
@override_settings(AXES_LOCKOUT_PARAMETERS=True)
def test_get_filter_kwargs_not_list_or_callable(self):
with self.assertRaises(TypeError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
@override_settings(AXES_LOCKOUT_PARAMETERS=lambda: None)
def test_get_filter_kwargs_invalid_callable_too_few_arguments(self):
with self.assertRaises(TypeError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
@override_settings(AXES_LOCKOUT_PARAMETERS=lambda request, credentials, extra: None)
def test_get_filter_kwargs_invalid_callable_too_many_arguments(self):
with self.assertRaises(TypeError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
@override_settings(
AXES_LOCKOUT_PARAMETERS=lambda request, credentials: ["wrong_param"]
)
@patch("axes.helpers.log")
def test_get_filter_kwargs_callable_invalid_lockout_param(self, log):
with self.assertRaises(ValueError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
log.exception.assert_called_with(
(
"wrong_param lockout parameter is not allowed. "
"Allowed lockout parameters: username, ip_address, user_agent"
)
)
@override_settings(
AXES_LOCKOUT_PARAMETERS=lambda request, credentials: [
["ip_address", "wrong_param"]
]
)
@patch("axes.helpers.log")
def test_get_filter_kwargs_callable_invalid_combined_lockout_param(self, log):
with self.assertRaises(ValueError):
get_client_parameters(
self.username,
self.ip_address,
self.user_agent,
self.request,
self.credentials,
)
log.exception.assert_called_with(
(
"wrong_param lockout parameter is not allowed. "
"Allowed lockout parameters: username, ip_address, user_agent"
)
)
class ClientCacheKeyTestCase(AxesTestCase):
def test_get_cache_key(self):
def test_get_cache_keys(self):
"""
Test the cache key format.
"""
@ -362,7 +605,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
"/admin/login/", data={"username": self.username, "password": "test"}
)
self.assertEqual([cache_hash_key], get_client_cache_key(request))
self.assertEqual([cache_hash_key], get_client_cache_keys(request))
# Getting cache key from AccessAttempt Object
attempt = AccessAttempt(
@ -376,7 +619,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
failures_since_start=0,
)
self.assertEqual([cache_hash_key], get_client_cache_key(attempt))
self.assertEqual([cache_hash_key], get_client_cache_keys(attempt))
def test_get_cache_key_empty_ip_address(self):
"""
@ -396,7 +639,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
REMOTE_ADDR=empty_ip_address,
)
self.assertEqual([cache_hash_key], get_client_cache_key(request))
self.assertEqual([cache_hash_key], get_client_cache_keys(request))
# Getting cache key from AccessAttempt Object
attempt = AccessAttempt(
@ -410,7 +653,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
failures_since_start=0,
)
self.assertEqual([cache_hash_key], get_client_cache_key(attempt))
self.assertEqual([cache_hash_key], get_client_cache_keys(attempt))
def test_get_cache_key_credentials(self):
"""
@ -430,7 +673,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
# Difference between the upper test: new call signature with credentials
credentials = {"username": self.username}
self.assertEqual([cache_hash_key], get_client_cache_key(request, credentials))
self.assertEqual([cache_hash_key], get_client_cache_keys(request, credentials))
# Getting cache key from AccessAttempt Object
attempt = AccessAttempt(
@ -443,7 +686,7 @@ class ClientCacheKeyTestCase(AxesTestCase):
path_info=request.META.get("PATH_INFO", "<unknown>"),
failures_since_start=0,
)
self.assertEqual([cache_hash_key], get_client_cache_key(attempt))
self.assertEqual([cache_hash_key], get_client_cache_keys(attempt))
class UsernameTestCase(AxesTestCase):
@ -554,6 +797,53 @@ def get_username(request, credentials: dict) -> str:
return "username"
def get_ip(request: HttpRequest) -> str:
return "127.0.0.1"
class ClientIpAddressTestCase(AxesTestCase):
@override_settings(AXES_CLIENT_IP_CALLABLE=get_ip)
def test_get_client_ip_address(self):
self.assertEqual(get_client_ip_address(HttpRequest()), "127.0.0.1")
@override_settings(AXES_CLIENT_IP_CALLABLE="tests.test_helpers.get_ip")
def test_get_client_ip_address_str(self):
self.assertEqual(get_client_ip_address(HttpRequest()), "127.0.0.1")
@override_settings(
AXES_CLIENT_IP_CALLABLE=lambda request: "127.0.0.1"
) # pragma: no cover
def test_get_client_ip_address_lambda(self):
self.assertEqual(get_client_ip_address(HttpRequest()), "127.0.0.1")
@override_settings(AXES_CLIENT_IP_CALLABLE=True)
def test_get_client_ip_address_not_callable(self):
with self.assertRaises(TypeError):
get_client_ip_address(HttpRequest())
@override_settings(AXES_CLIENT_IP_CALLABLE=lambda: None) # pragma: no cover
def test_get_client_ip_address_invalid_callable_too_few_arguments(self):
with self.assertRaises(TypeError):
get_client_ip_address(HttpRequest())
@override_settings(
AXES_CLIENT_IP_CALLABLE=lambda request, extra: None
) # pragma: no cover
def test_get_client_ip_address_invalid_callable_too_many_arguments(self):
with self.assertRaises(TypeError):
get_client_ip_address(HttpRequest())
def test_get_client_ip_address_with_ipware(self):
request = HttpRequest()
request.META["REMOTE_ADDR"] = "127.0.0.2"
self.assertEqual(get_client_ip_address(request, use_ipware=True), "127.0.0.2")
def test_get_client_ip_address_without_ipware(self):
request = HttpRequest()
request.META["REMOTE_ADDR"] = "127.0.0.3"
self.assertEqual(get_client_ip_address(request, use_ipware=False), "127.0.0.3")
class IPWhitelistTestCase(AxesTestCase):
def setUp(self):
self.request = HttpRequest()
@ -657,7 +947,7 @@ class LockoutResponseTestCase(AxesTestCase):
self.assertEqual(type(response), HttpResponse)
def mock_get_cool_off_str():
def mock_get_cool_off_str(req):
return timedelta(seconds=30)
@ -671,18 +961,18 @@ class AxesCoolOffTestCase(AxesTestCase):
self.assertEqual(get_cool_off(), timedelta(hours=2))
@override_settings(AXES_COOLOFF_TIME=2.0)
def test_get_cool_off_int(self):
def test_get_cool_off_float(self):
self.assertEqual(get_cool_off(), timedelta(minutes=120))
@override_settings(AXES_COOLOFF_TIME=0.25)
def test_get_cool_off_int(self):
def test_get_cool_off_float_lt_0(self):
self.assertEqual(get_cool_off(), timedelta(minutes=15))
@override_settings(AXES_COOLOFF_TIME=1.7)
def test_get_cool_off_int(self):
def test_get_cool_off_float_gt_0(self):
self.assertEqual(get_cool_off(), timedelta(seconds=6120))
@override_settings(AXES_COOLOFF_TIME=lambda: timedelta(seconds=30))
@override_settings(AXES_COOLOFF_TIME=lambda r: timedelta(seconds=30))
def test_get_cool_off_callable(self):
self.assertEqual(get_cool_off(), timedelta(seconds=30))
@ -723,19 +1013,26 @@ def mock_get_lockout_response(request, credentials):
return HttpResponse(status=400)
def mock_get_lockout_response_with_original_response_param(
request, response, credentials
):
return HttpResponse(status=400)
class AxesLockoutTestCase(AxesTestCase):
def setUp(self):
self.request = HttpRequest()
self.response = HttpResponse()
self.credentials = dict()
def test_get_lockout_response(self):
response = get_lockout_response(self.request, self.credentials)
self.assertEqual(403, response.status_code)
self.assertEqual(429, response.status_code)
@override_settings(AXES_HTTP_RESPONSE_CODE=429)
@override_settings(AXES_HTTP_RESPONSE_CODE=403)
def test_get_lockout_response_with_custom_http_response_code(self):
response = get_lockout_response(self.request, self.credentials)
self.assertEqual(429, response.status_code)
self.assertEqual(403, response.status_code)
@override_settings(AXES_LOCKOUT_CALLABLE=mock_get_lockout_response)
def test_get_lockout_response_override_callable(self):
@ -749,6 +1046,20 @@ class AxesLockoutTestCase(AxesTestCase):
response = get_lockout_response(self.request, self.credentials)
self.assertEqual(400, response.status_code)
@override_settings(
AXES_LOCKOUT_CALLABLE=mock_get_lockout_response_with_original_response_param
)
def test_get_lockout_response_override_callable_with_original_response_param(self):
response = get_lockout_response(self.request, self.response, self.credentials)
self.assertEqual(400, response.status_code)
@override_settings(
AXES_LOCKOUT_CALLABLE="tests.test_helpers.mock_get_lockout_response_with_original_response_param"
)
def test_get_lockout_response_override_path_with_original_response_param(self):
response = get_lockout_response(self.request, self.response, self.credentials)
self.assertEqual(400, response.status_code)
@override_settings(AXES_LOCKOUT_CALLABLE=42)
def test_get_lockout_response_override_invalid(self):
with self.assertRaises(TypeError):
@ -763,6 +1074,7 @@ class AxesCleanseParamsTestCase(AxesTestCase):
"other_sensitive_data": "sensitive",
}
@override_settings(AXES_SENSITIVE_PARAMETERS=[])
def test_cleanse_parameters(self):
cleansed = cleanse_parameters(self.parameters)
self.assertEqual("test_user", cleansed["username"])
@ -784,6 +1096,7 @@ class AxesCleanseParamsTestCase(AxesTestCase):
self.assertEqual("********************", cleansed["password"])
self.assertEqual("********************", cleansed["other_sensitive_data"])
@override_settings(AXES_SENSITIVE_PARAMETERS=[])
@override_settings(AXES_PASSWORD_FORM_FIELD=None)
def test_cleanse_parameters_override_empty(self):
cleansed = cleanse_parameters(self.parameters)

View file

@ -1,15 +1,14 @@
from unittest.mock import patch
from django.test import override_settings
from django.urls import reverse
from pkg_resources import get_distribution
from axes import __version__
from axes.apps import AppConfig
from axes.models import AccessAttempt, AccessLog
from tests.base import AxesTestCase
_BEGIN = "AXES: BEGIN version %s, %s"
_VERSION = get_distribution("django-axes").version
_VERSION = __version__
@patch("axes.apps.AppConfig.initialized", False)
@ -34,48 +33,55 @@ class AppsTestCase(AxesTestCase):
AppConfig.initialize()
self.assertFalse(log.info.called)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_axes_config_log_user_only(self, log):
AppConfig.initialize()
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username only")
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username")
@override_settings(AXES_ONLY_USER_FAILURES=False)
def test_axes_config_log_ip_only(self, log):
AppConfig.initialize()
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by IP only")
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by ip_address")
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_axes_config_log_user_ip(self, log):
AppConfig.initialize()
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by combination of username and IP")
log.info.assert_called_with(
_BEGIN, _VERSION, "blocking by combination of username and ip_address"
)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_axes_config_log_user_or_ip(self, log):
AppConfig.initialize()
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username or IP")
log.info.assert_called_with(_BEGIN, _VERSION, "blocking by username or ip_address")
class AccessLogTestCase(AxesTestCase):
def test_access_log_on_logout(self):
"""
Test a valid logout and make sure the logout_time is updated.
Test a valid logout and make sure the logout_time is updated only for that.
"""
self.login(is_valid_username=True, is_valid_password=True)
self.assertIsNone(AccessLog.objects.latest("id").logout_time)
latest_log = AccessLog.objects.latest("id")
self.assertIsNone(latest_log.logout_time)
other_log = self.create_log(session_hash='not-the-session')
self.assertIsNone(other_log.logout_time)
response = self.client.get(reverse("admin:logout"))
response = self.logout()
self.assertContains(response, "Logged out")
other_log.refresh_from_db()
self.assertIsNone(other_log.logout_time)
latest_log.refresh_from_db()
self.assertIsNotNone(latest_log.logout_time)
self.assertIsNotNone(AccessLog.objects.latest("id").logout_time)
@override_settings(DATA_UPLOAD_MAX_NUMBER_FIELDS=1500)
def test_log_data_truncated(self):
"""
Test that get_query_str properly truncates data to the max_length (default 1024).
"""
# An impossibly large post dict
extra_data = {"a" * x: x for x in range(1024)}
extra_data = {"too-large-field": "x" * 2 ** 16}
self.login(**extra_data)
self.assertEqual(len(AccessAttempt.objects.latest("id").post_data), 1024)
@ -84,7 +90,7 @@ class AccessLogTestCase(AxesTestCase):
AccessLog.objects.all().delete()
response = self.login(is_valid_username=True, is_valid_password=True)
response = self.client.get(reverse("admin:logout"))
response = self.logout()
self.assertEqual(AccessLog.objects.all().count(), 0)
self.assertContains(response, "Logged out", html=True)
@ -107,7 +113,7 @@ class AccessLogTestCase(AxesTestCase):
AccessLog.objects.all().delete()
response = self.login(is_valid_username=True, is_valid_password=True)
response = self.client.get(reverse("admin:logout"))
response = self.logout()
self.assertEqual(AccessLog.objects.count(), 0)
self.assertContains(response, "Logged out", html=True)

View file

@ -28,7 +28,7 @@ class DjangoLoginTestCase(TestCase):
self.username = "john.doe"
self.password = "hunter2"
self.user = get_user_model().objects.create(username=self.username)
self.user = get_user_model().objects.create(username=self.username, is_staff=True)
self.user.set_password(self.password)
self.user.save()
self.user.backend = "django.contrib.auth.backends.ModelBackend"
@ -47,13 +47,19 @@ class DjangoContribAuthLoginTestCase(DjangoLoginTestCase):
class DjangoTestClientLoginTestCase(DjangoLoginTestCase):
def test_client_login(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get(reverse("admin:index"))
self.assertEqual(response.status_code, 200)
def test_client_logout(self):
self.client.login(username=self.username, password=self.password)
self.client.logout()
response = self.client.get(reverse("admin:index"))
self.assertEqual(response.status_code, 302)
def test_client_force_login(self):
self.client.force_login(self.user)
response = self.client.get(reverse("admin:index"))
self.assertEqual(response.status_code, 200)
class DatabaseLoginTestCase(AxesTestCase):
@ -84,9 +90,9 @@ class DatabaseLoginTestCase(AxesTestCase):
LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
ATTEMPT_NOT_BLOCKED = 200
ALLOWED = 302
BLOCKED = 403
BLOCKED = 429
def _login(self, username, password, ip_addr="127.0.0.1", **kwargs):
def _login(self, username, password, ip_addr="127.0.0.1", user_agent="test-browser", **kwargs):
"""
Login a user and get the response.
@ -101,13 +107,13 @@ class DatabaseLoginTestCase(AxesTestCase):
reverse("admin:login"),
post_data,
REMOTE_ADDR=ip_addr,
HTTP_USER_AGENT="test-browser",
HTTP_USER_AGENT=user_agent,
)
def _lockout_user_from_ip(self, username, ip_addr):
def _lockout_user_from_ip(self, username, ip_addr, user_agent="test-browser"):
for _ in range(settings.AXES_FAILURE_LIMIT):
response = self._login(
username=username, password=self.WRONG_PASSWORD, ip_addr=ip_addr
username=username, password=self.WRONG_PASSWORD, ip_addr=ip_addr, user_agent=user_agent,
)
return response
@ -182,10 +188,11 @@ class DatabaseLoginTestCase(AxesTestCase):
self.assertContains(response, self.LOCKED_MESSAGE, status_code=self.BLOCKED)
self.assertTrue(self.attempt_count())
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_combination_user_and_ip(self):
"""
Test login failure when AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP is True.
Test login failure when lockout parameters is combination
of username and ip_address.
"""
# test until one try before the limit
@ -197,12 +204,12 @@ class DatabaseLoginTestCase(AxesTestCase):
# So, we shouldn't have gotten a lock-out yet.
# But we should get one now
response = self.login(is_valid_username=True, is_valid_password=False)
self.assertContains(response, self.LOCKED_MESSAGE, status_code=403)
self.assertContains(response, self.LOCKED_MESSAGE, status_code=429)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_only_user_failures(self):
"""
Test login failure when AXES_ONLY_USER_FAILURES is True.
Test login failure when lockout parameter is username.
"""
# test until one try before the limit
@ -238,6 +245,139 @@ class DatabaseLoginTestCase(AxesTestCase):
response, self.LOGIN_FORM_KEY, status_code=self.ALLOWED, html=True
)
@override_settings(AXES_LOCKOUT_PARAMETERS=["user_agent"])
def test_lockout_by_user_agent_only(self):
"""
Test login failure when lockout parameter is only user_agent
"""
# User is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked with another username:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked with another ip:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test with another user agent:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser-2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
@override_settings(AXES_LOCKOUT_PARAMETERS=["ip_address", "username", "user_agent"])
def test_lockout_by_all_parameters(self):
# User is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by username:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by ip:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser2")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by user_agent:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is allowed to login with different username, ip and user_agent
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "username", "user_agent"]])
def test_lockout_by_combination_of_all_parameters(self):
# User is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is allowed to login with different username:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different IP:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different user_agent:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different username, ip and user_agent
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
@override_settings(AXES_LOCKOUT_PARAMETERS=["ip_address", ["username", "user_agent"]])
def test_lockout_by_ip_or_username_and_user_agent(self):
# User is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by ip:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser2")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by username and user_agent:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is allowed to login with different username and ip
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different user_agent and ip
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different username, ip and user_agent
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"], ["username", "user_agent"]])
def test_lockout_by_ip_and_user_agent_or_username_and_user_agent(self):
# User is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by ip and user_agent:
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is locked by username and user_agent:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test he is allowed to login with different username and ip
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different user_agent
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test he is allowed to login with different username, ip and user_agent
response = self._login("username2", self.VALID_PASSWORD, ip_addr=self.IP_2, user_agent="test-browser2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test for true and false positives when blocking by IP *OR* user (default)
# Cache disabled. Default settings.
def test_lockout_by_ip_blocks_when_same_user_same_ip_without_cache(self):
@ -274,7 +414,7 @@ class DatabaseLoginTestCase(AxesTestCase):
# Test for true and false positives when blocking by user only.
# Cache disabled. When AXES_ONLY_USER_FAILURES = True
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_blocks_when_same_user_same_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -283,7 +423,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_blocks_when_same_user_diff_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -292,7 +432,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_allows_when_diff_user_same_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -301,7 +441,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_allows_when_diff_user_diff_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -310,7 +450,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_with_empty_username_allows_other_users_without_cache(self):
# User with empty username is locked out from IP 1.
self._lockout_user_from_ip(username="", ip_addr=self.IP_1)
@ -321,7 +461,7 @@ class DatabaseLoginTestCase(AxesTestCase):
# Test for true and false positives when blocking by user and IP together.
# Cache disabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_blocks_when_same_user_same_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -330,7 +470,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_same_user_diff_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -339,7 +479,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -348,7 +488,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_diff_user_diff_ip_without_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -357,7 +497,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_without_cache(
self,
):
@ -368,6 +508,19 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self.client.get(reverse("admin:login"), REMOTE_ADDR=self.IP_1)
self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["ip_address", "user_agent"]])
def test_lockout_by_user_still_allows_login_with_differnet_user_agent(self):
# User with empty username is locked out with "test-browser" user agent.
self._lockout_user_from_ip(username="username", ip_addr=self.IP_1, user_agent="test-browser")
# Test he is locked:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser")
self.assertEqual(response.status_code, self.BLOCKED)
# Test with another user agent:
response = self._login("username", self.VALID_PASSWORD, ip_addr=self.IP_1, user_agent="test-browser-2")
self.assertEqual(response.status_code, self.ATTEMPT_NOT_BLOCKED)
# Test for true and false positives when blocking by IP *OR* user (default)
# With cache enabled. Default criteria.
def test_lockout_by_ip_blocks_when_same_user_same_ip_using_cache(self):
@ -402,7 +555,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_with_empty_username_allows_other_users_using_cache(self):
# User with empty username is locked out from IP 1.
self._lockout_user_from_ip(username="", ip_addr=self.IP_1)
@ -413,7 +566,7 @@ class DatabaseLoginTestCase(AxesTestCase):
# Test for true and false positives when blocking by user only.
# With cache enabled. When AXES_ONLY_USER_FAILURES = True
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_blocks_when_same_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -422,7 +575,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_blocks_when_same_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -431,7 +584,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_allows_when_diff_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -440,7 +593,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_ONLY_USER_FAILURES=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username"])
def test_lockout_by_user_allows_when_diff_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -451,7 +604,7 @@ class DatabaseLoginTestCase(AxesTestCase):
# Test for true and false positives when blocking by user and IP together.
# With cache enabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_blocks_when_same_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -460,7 +613,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_same_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -469,7 +622,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -478,7 +631,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_allows_when_diff_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -488,7 +641,7 @@ class DatabaseLoginTestCase(AxesTestCase):
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True, AXES_FAILURE_LIMIT=2
AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]], AXES_FAILURE_LIMIT=2
)
def test_lockout_by_user_and_ip_allows_when_diff_user_same_ip_using_cache_multiple_attempts(
self,
@ -517,7 +670,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=[["username", "ip_address"]])
def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_using_cache(
self,
):
@ -530,7 +683,7 @@ class DatabaseLoginTestCase(AxesTestCase):
# Test for true and false positives when blocking by user or IP together.
# With cache enabled. When AXES_LOCK_OUT_BY_USER_OR_IP = True
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_lockout_by_user_or_ip_blocks_when_same_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -539,7 +692,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_lockout_by_user_or_ip_allows_when_same_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -548,7 +701,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_1, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_lockout_by_user_or_ip_allows_when_diff_user_same_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -557,7 +710,9 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_1)
self.assertEqual(response.status_code, self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True, AXES_FAILURE_LIMIT=3)
@override_settings(
AXES_LOCKOUT_PARAMETERS=["username", "ip_address"], AXES_FAILURE_LIMIT=3
)
def test_lockout_by_user_or_ip_allows_when_diff_user_same_ip_using_cache_multiple_attempts(
self,
):
@ -587,7 +742,9 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_3, self.WRONG_PASSWORD, ip_addr=self.IP_1)
self.assertContains(response, self.LOCKED_MESSAGE, status_code=self.BLOCKED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True, AXES_FAILURE_LIMIT=3)
@override_settings(
AXES_LOCKOUT_PARAMETERS=["username", "ip_address"], AXES_FAILURE_LIMIT=3
)
def test_lockout_by_user_or_ip_allows_when_diff_user_same_ip_using_cache_multiple_failed_attempts(
self,
):
@ -612,7 +769,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_lockout_by_user_or_ip_allows_when_diff_user_diff_ip_using_cache(self):
# User 1 is locked out from IP 1.
self._lockout_user1_from_ip1()
@ -621,7 +778,7 @@ class DatabaseLoginTestCase(AxesTestCase):
response = self._login(self.USER_2, self.VALID_PASSWORD, ip_addr=self.IP_2)
self.assertEqual(response.status_code, self.ALLOWED)
@override_settings(AXES_LOCK_OUT_BY_USER_OR_IP=True)
@override_settings(AXES_LOCKOUT_PARAMETERS=["username", "ip_address"])
def test_lockout_by_user_or_ip_with_empty_username_allows_other_users_using_cache(
self,
):

View file

@ -56,18 +56,22 @@ class ManagementCommandTestCase(AxesTestCase):
username="john.doe", ip_address="10.0.0.2", failures_since_start="15"
)
AccessAttempt.objects.create(
username="richard.doe", ip_address="10.0.0.4", failures_since_start="12"
)
def test_axes_list_attempts(self):
out = StringIO()
call_command("axes_list_attempts", stdout=out)
expected = "10.0.0.1\tjane.doe\t4\n10.0.0.2\tjohn.doe\t15\n"
expected = "10.0.0.1\tjane.doe\t4\n10.0.0.2\tjohn.doe\t15\n10.0.0.4\trichard.doe\t12\n"
self.assertEqual(expected, out.getvalue())
def test_axes_reset(self):
out = StringIO()
call_command("axes_reset", stdout=out)
expected = "2 attempts removed.\n"
expected = "3 attempts removed.\n"
self.assertEqual(expected, out.getvalue())
def test_axes_reset_not_found(self):
@ -87,6 +91,13 @@ class ManagementCommandTestCase(AxesTestCase):
expected = "1 attempts removed.\n"
self.assertEqual(expected, out.getvalue())
def test_axes_reset_ip_username(self):
out = StringIO()
call_command("axes_reset_ip_username", "10.0.0.4", "richard.doe", stdout=out)
expected = "1 attempts removed.\n"
self.assertEqual(expected, out.getvalue())
def test_axes_reset_ip_not_found(self):
out = StringIO()
call_command("axes_reset_ip", "10.0.0.3", stdout=out)

View file

@ -12,7 +12,7 @@ def get_username(request, credentials: dict) -> str:
class MiddlewareTestCase(AxesTestCase):
STATUS_SUCCESS = 200
STATUS_LOCKOUT = 403
STATUS_LOCKOUT = 429
def setUp(self):
self.request = HttpRequest()