New upstream version 4.4.0
James Valleroy
5 years ago
0 | 0 | Changes |
1 | 1 | ======= |
2 | ||
3 | 4.4.0 (2018-05-26) | |
4 | ------------------ | |
5 | ||
6 | - Added AXES_USERNAME_CALLABLE | |
7 | [jaadus] | |
8 | ||
2 | 9 | |
3 | 10 | 4.3.1 (2018-04-21) |
4 | 11 | ------------------ |
0 | 0 | from __future__ import unicode_literals |
1 | 1 | |
2 | __version__ = '4.3.1' | |
2 | __version__ = '4.4.0' | |
3 | 3 | |
4 | 4 | default_app_config = 'axes.apps.AppConfig' |
5 | 5 |
57 | 57 | def has_add_permission(self, request): |
58 | 58 | return False |
59 | 59 | |
60 | ||
60 | 61 | admin.site.register(AccessAttempt, AccessAttemptAdmin) |
61 | 62 | |
62 | 63 | |
108 | 109 | def has_add_permission(self, request): |
109 | 110 | return False |
110 | 111 | |
112 | ||
111 | 113 | admin.site.register(AccessLog, AccessLogAdmin) |
7 | 7 | |
8 | 8 | from axes.conf import settings |
9 | 9 | from axes.models import AccessAttempt |
10 | from axes.utils import get_axes_cache, get_client_ip | |
10 | from axes.utils import get_axes_cache, get_client_ip, get_client_username | |
11 | 11 | |
12 | 12 | |
13 | 13 | def _query_user_attempts(request): |
15 | 15 | Otherwise return None. |
16 | 16 | """ |
17 | 17 | ip = get_client_ip(request) |
18 | username = request.POST.get(settings.AXES_USERNAME_FORM_FIELD, None) | |
18 | username = get_client_username(request) | |
19 | 19 | |
20 | 20 | if settings.AXES_ONLY_USER_FAILURES: |
21 | 21 | attempts = AccessAttempt.objects.filter(username=username) |
157 | 157 | try: |
158 | 158 | field = getattr(get_user_model(), 'USERNAME_FIELD', 'username') |
159 | 159 | kwargs = { |
160 | field: request.POST.get(settings.AXES_USERNAME_FORM_FIELD) | |
160 | field: get_client_username(request) | |
161 | 161 | } |
162 | 162 | user = get_user_model().objects.get(**kwargs) |
163 | 163 |
18 | 18 | |
19 | 19 | # use a specific password field to retrieve from login POST data |
20 | 20 | PASSWORD_FORM_FIELD = 'password' |
21 | ||
22 | # use a provided callable to transform the POSTed username into the one used in credentials | |
23 | USERNAME_CALLABLE = None | |
21 | 24 | |
22 | 25 | # only check user name and not location or user_agent |
23 | 26 | ONLY_USER_FAILURES = False |
11 | 11 | from axes import get_version |
12 | 12 | from axes.conf import settings |
13 | 13 | from axes.attempts import is_already_locked |
14 | from axes.utils import iso8601, get_lockout_message | |
14 | from axes.utils import iso8601, get_client_username, get_lockout_message | |
15 | 15 | |
16 | 16 | log = logging.getLogger(settings.AXES_LOGGER) |
17 | 17 | if settings.AXES_VERBOSE: |
49 | 49 | def lockout_response(request): |
50 | 50 | context = { |
51 | 51 | 'failure_limit': settings.AXES_FAILURE_LIMIT, |
52 | 'username': request.POST.get(settings.AXES_USERNAME_FORM_FIELD, '') | |
52 | 'username': get_client_username(request) or '' | |
53 | 53 | } |
54 | 54 | |
55 | 55 | cool_off = settings.AXES_COOLOFF_TIME |
1 | 1 | |
2 | 2 | import datetime |
3 | 3 | |
4 | from django.http import HttpRequest | |
4 | 5 | from django.test import TestCase, override_settings |
5 | 6 | from django.utils import six |
6 | 7 | |
7 | from axes.utils import iso8601, is_ipv6, get_client_str | |
8 | from axes.utils import iso8601, is_ipv6, get_client_str, get_client_username | |
8 | 9 | |
9 | 10 | |
10 | 11 | class UtilsTest(TestCase): |
142 | 143 | actual = get_client_str(username, ip, user_agent, path_info) |
143 | 144 | |
144 | 145 | self.assertEqual(expected, actual) |
146 | ||
147 | @override_settings(AXES_USERNAME_FORM_FIELD='username') | |
148 | def test_default_get_client_username(self): | |
149 | expected = 'test-username' | |
150 | ||
151 | request = HttpRequest() | |
152 | request.POST['username'] = expected | |
153 | ||
154 | actual = get_client_username(request) | |
155 | ||
156 | self.assertEqual(expected, actual) | |
157 | ||
158 | def sample_customize_username(request): | |
159 | return 'prefixed-' + request.POST.get('username') | |
160 | ||
161 | @override_settings(AXES_USERNAME_FORM_FIELD='username') | |
162 | @override_settings(AXES_USERNAME_CALLABLE=sample_customize_username) | |
163 | def test_custom_get_client_username(self): | |
164 | provided = 'test-username' | |
165 | expected = 'prefixed-' + provided | |
166 | ||
167 | request = HttpRequest() | |
168 | request.POST['username'] = provided | |
169 | ||
170 | actual = get_client_username(request) | |
171 | ||
172 | self.assertEqual(expected, actual) |
68 | 68 | return getattr(request, client_ip_attribute) |
69 | 69 | |
70 | 70 | |
71 | def get_client_username(request): | |
72 | if settings.AXES_USERNAME_CALLABLE: | |
73 | return settings.AXES_USERNAME_CALLABLE(request) | |
74 | return request.POST.get(settings.AXES_USERNAME_FORM_FIELD, None) | |
75 | ||
76 | ||
71 | 77 | def is_ipv6(ip): |
72 | 78 | try: |
73 | 79 | inet_pton(AF_INET6, ip) |
123 | 123 | Default: ``True`` |
124 | 124 | * ``AXES_USERNAME_FORM_FIELD``: the name of the form field that contains your |
125 | 125 | users usernames. Default: ``username`` |
126 | * ``AXES_USERNAME_CALLABLE``: A callable function that takes a request object | |
127 | and returns the username. If empty, axes just fetches it from the POST body | |
128 | based on ``AXES_USERNAME_FORM_FIELD``. Default: ``None`` | |
126 | 129 | * ``AXES_PASSWORD_FORM_FIELD``: the name of the form field that contains your |
127 | 130 | users password. Default: ``password`` |
128 | 131 | * ``AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP``: If ``True`` prevents the login |
176 | 176 | url(r'^accounts/', include('allauth.urls')), |
177 | 177 | # ... |
178 | 178 | ] |
179 | ||
180 | Altering username before login | |
181 | ------------------------------ | |
182 | ||
183 | In special cases, you may have the need to modify the username that is | |
184 | submitted before attempting to authenticate. For example, adding namespacing or | |
185 | removing client-set prefixes. In these cases, ``axes`` needs to know how to make | |
186 | these changes so that it can correctly identify the user without any form | |
187 | cleaning or validation. This is where the ``AXES_USERNAME_CALLABLE`` setting | |
188 | comes in. You can define how to make these modifications in a callable that | |
189 | takes a request object, and provide that callable to ``axes`` via this setting. | |
190 | ||
191 | For example, a function like this could take a post body with something like | |
192 | ``username='prefixed-username'`` and ``namespace=my_namespace`` and turn it | |
193 | into ``my_namespace-username``: | |
194 | ||
195 | *settings.py:* :: | |
196 | ||
197 | def sample_username_modifier(request): | |
198 | provided_username = request.POST.get('username') | |
199 | some_namespace = request.POST.get('namespace') | |
200 | return '-'.join([some_namespace, provided_username[9:]]) | |
201 | ||
202 | AXES_USERNAME_CALLABLE = sample_username_modifier | |
203 | ||
204 | NOTE: You still have to make these modifications yourself before calling | |
205 | authenticate. If you want to re-use the same function for consistency, that's | |
206 | fine, but ``axes`` doesn't inject these changes into the authentication flow | |
207 | for you. |