jazzband / django-defender

A simple super fast django reusable app that blocks people from brute forcing login attempts
Apache License 2.0
1.04k stars 142 forks source link

How to use with OAUTH2 - django-oauth-toolkit? #124

Open bpsgit opened 6 years ago

bpsgit commented 6 years ago

Hi,

Trying to use with OAUTH2 - django-oauth-toolkit.

MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'oauth2_provider.middleware.OAuth2TokenMiddleware', 'defender.middleware.FailedLoginMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', ) User blocking works fine in case of post on /admin/login/ Does not work on /accounts/login/

Thanks in advance.

dopry commented 2 years ago

@bpsgit did you ever figure this out?

dopry commented 2 years ago

I just figured out how to use it with the OAuthResourceOwnerPassword grant type... I needed to implement a CustomOAuth2Validator, and override the proper method for the login validation used by the grant type... There was one major gotcha, OAuthLib/OAuthToolkit have a different request class than django that doesnt have META, so I needed to shim that... and I also needed to figure out which errors to raise by looking at the method in the GrantType that was calling the Validator.validate_user... If you're working with another grant type, you'll probably need to shim a different method.

Hope this helps.

from defender.utils import add_login_attempt_to_db, check_request, is_already_locked
from django.contrib.auth import authenticate
from oauth2_provider.oauth2_validators import OAuth2Validator
from oauthlib.oauth2.rfc6749 import errors

class CustomOAuth2Validator(OAuth2Validator):
    # this method is called by oauthlib.oauth2.rfc6749.grant_types.ResourceOwnerPasswordCredentialsGrant
    # it looks like that is the only grant type that calls this method. 
    def validate_user(self, username, password, client, request, *args, **kwargs):
        """
        Check username and password correspond to a valid and active User,
        enforce django defender on authentication. 
        """
        # django-defender expects https://docs.djangoproject.com/en/3.2/ref/request-response/#django.http.HttpRequest.META
        # but OAuthLib.core.Request makes a simplified version without META
        # so we need to shim it here to make defender happy.
        request.META = request.headers

        locked = is_already_locked(request, username=username)
        # print('locked', locked)
        # print('request', request)
        # print('request', request.body)
        if locked:
            raise errors.InvalidGrantError(description='Too many failed login attempts.', status_code=403, request=request)

        u = authenticate(username=username, password=password)
        login_valid =  u is not None and u.is_active

        add_login_attempt_to_db(request, login_valid=login_valid, username=username)
        check_request(request, login_unsuccessful=not login_valid, username=username)

        if not login_valid: 
            raise errors.InvalidGrantError('Invalid credentials given.', status_code=401, request=request)

        request.user = u
        return login_valid