pulseapi/users/utils.py (53 lines of code) (raw):

import json from django.conf import settings from django.core.exceptions import PermissionDenied from django.http import HttpResponseForbidden from django.views.generic.base import RedirectView from allauth.account.views import LoginView from requests import post from requests.exceptions import RequestException # see https://developers.google.com/recaptcha/docs/verify RECAPTCHA_VERIFICATION_URL = 'https://www.google.com/recaptcha/api/siteverify' def verify_recaptcha(response_token): """ Ask google to verify a client-generated recaptcha token against what it supposedly generated. """ try: response = post(RECAPTCHA_VERIFICATION_URL, timeout=5, data={ 'response': response_token, 'secret': settings.RECAPTCHA_SECRET, }) response.raise_for_status() except RequestException: return False data = json.loads(response.text) if data.get('success') is not True: return False return True __old_get_context_data = LoginView.get_context_data def augmented_get_context_data(self, **kwargs): """ Patch allauth's LoginView.get_context_data class function so that we can check for a recaptcha-related session value if we're using recaptcha. Allauth only has post-processing signals, so we're kind of left with monkey patching as the only way to pre-process the login route. """ if settings.USE_RECAPTCHA: request = self.request session = request.session if 'recaptcha_token' not in session: raise PermissionDenied() session_token = session['recaptcha_token'] # clear the token after reading it so someone can't just set a valid session and then # bulk-load the allauth route with a hundred tabs. session['recaptcha_token'] = None if session_token is None: raise PermissionDenied() client_token = request.GET.get('token', None) if client_token is None: raise PermissionDenied() if client_token != session_token: raise PermissionDenied() return __old_get_context_data(self, **kwargs) LoginView.get_context_data = augmented_get_context_data class LoginRedirectView(RedirectView): """ A utility view that redirects requests to the real login route, but only if recaptcha validation passes (provided recaptcha is enabled). If recaptcha fails, we send an http 403 response. """ permanent = False query_string = True url = '/accounts/login' def get(self, request, *args, **kwargs): if settings.USE_RECAPTCHA: client_token = request.GET.get('token', None) if client_token is None: return HttpResponseForbidden() if not verify_recaptcha(client_token): return HttpResponseForbidden() """ note the token in the session, so that the redirect to `accounts/login` works. This allows us to write code that prevents users from directly accessing the allauth login route, thus preventing recaptcha circumvention. """ request.session['recaptcha_token'] = client_token return super().get(request, *args, **kwargs)