Example djangorestframework auth method

- sample authentication method described in README
piggyback:
- typo in lockout.html
This commit is contained in:
Jakub Kuszneruk 2017-03-21 00:32:11 +01:00 committed by Ken Cochrane
parent 46ea25c8f0
commit 079c897203
2 changed files with 84 additions and 1 deletions

View file

@ -71,6 +71,7 @@ Features
- list of blocked usernames and ip's
- ability to unblock people
- list of recent login attempts
- Can be easly adapted to custom authentication method.
Long term goals
===============
@ -340,6 +341,88 @@ long to keep the access attempt records in the database before the management
command cleans them up.
[Default: ``24``]
Adapting to other authentication method
--------------------
`defender` can be used for authentication other than `Django authentication system`.
E.g. if `django-rest-framework` authentication has to be protected from brute force attack,custom authentication method can be implemented.
There's sample `BasicAuthenticationDefender` class based on `djangorestframework.BasicAuthentication`:
```python
import base64
import binascii
from defender import utils
from defender import config
from django.utils.translation import ugettext_lazy as _
from rest_framework import HTTP_HEADER_ENCODING, exceptions
from rest_framework.authentication import (
BasicAuthentication,
get_authorization_header,
)
class BasicAuthenticationDefender(BasicAuthentication):
def get_username_from_request(self, request):
auth = get_authorization_header(request).split()
return base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')[0]
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'basic':
return None
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
if utils.is_already_locked(request, get_username=self.get_username_from_request):
detail = "You have attempted to login {failure_limit} times, with no success." \
"Your account is locked for {cooloff_time_seconds} seconds" \
"".format(
failure_limit=config.FAILURE_LIMIT,
cooloff_time_seconds=config.COOLOFF_TIME
)
raise exceptions.AuthenticationFailed(_(detail))
try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2]
login_unsuccessful = False
login_exception = None
try:
response = self.authenticate_credentials(userid, password)
except exceptions.AuthenticationFailed as e:
login_unsuccessful = True
login_exception = e
utils.add_login_attempt_to_db(request,
login_valid=not login_unsuccessful,
get_username=self.get_username_from_request)
user_not_blocked = utils.check_request(request,
login_unsuccessful=login_unsuccessful,
get_username=self.get_username_from_request)
if user_not_blocked and not login_unsuccessful:
return response
raise login_exception
```
To make it works add `BasicAuthenticationDefender` to `DEFAULT_AUTHENTICATION_CLASSES` in your `settings.py`.
Running Tests
=============

View file

@ -1,7 +1,7 @@
<html>
<body>
<h1>Locked out</h1>
<p>Your have attempted to login {{failure_limit}} times, with no success.
<p>You have attempted to login {{failure_limit}} times, with no success.
Your account is locked for {{cooloff_time_seconds}} seconds</p>
</body>
</html>