1

I have the following Decorator which works fine when applied to different views with: @otp_required(login_url='login') on my site:

Decorator

from django.contrib.auth.decorators import user_passes_test

from django_otp import user_has_device
from django_otp.conf import settings


def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
    """
    Similar to :func:`~django.contrib.auth.decorators.login_required`, but
    requires the user to be :term:`verified`. By default, this redirects users
    to :setting:`OTP_LOGIN_URL`.

    :param if_configured: If ``True``, an authenticated user with no confirmed
        OTP devices will be allowed. Default is ``False``.
    :type if_configured: bool
    """
    if login_url is None:
        login_url = settings.OTP_LOGIN_URL

    def test(user):
        return user.is_verified() or (if_configured and user.is_authenticated and not user_has_device(user))

    decorator = user_passes_test(test, login_url=login_url, redirect_field_name=redirect_field_name)

    return decorator if (view is None) else decorator(view)

However, I’d like to convert this into a Middleware as I want to avoid having to apply a decorator to every view on my site, but not managed to get working. I tried to amend the following Middleware which I currently have in place which is just for authorised users and has been working but as per above decorator I want this Middleware extended to also have OTP required as well:

Middleware

from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device
from django_otp.decorators import otp_required
from django_otp.middleware import is_verified

class LoginRequiredMiddleware(MiddlewareMixin):
    """
    Middleware that requires a user to be authenticated to view any page other
    than LOGIN_URL. Exemptions to this requirement can optionally be specified
    in settings by setting a tuple of routes to ignore
    """

    #@otp_required(login_url='login')
    def process_request(self, request):
        assert hasattr(request, 'user'), """
        The Login Required middleware needs to be after AuthenticationMiddleware.
        Also make sure to include the template context_processor:
        'django.contrib.account.context_processors.account'."""


        if not request.user.is_verified() and not request.path.startswith('/admin/') and not request.path.startswith('/account/' ):

            current_route_name = resolve(request.path_info).url_name

            if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
                return HttpResponseRedirect(reverse(settings.LOGIN_URL))

Help is much appreciated.

rob
  • 143
  • 1
  • 9

1 Answers1

0

The fact that you return a HttpResponseRedirect will not work: Django's MiddlewareMixin will simply call the function to (optionally) alter the request, but it will never take the return into account.

What you can do is define middleware in a decorator-like structure, and return the HttpResponseRedirect in case the user should be authenticated with:

from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings

def OTPRequiredMiddleware(get_response):
    """
    Middleware that requires a user to be authenticated to view any page other
    than LOGIN_URL. Exemptions to this requirement can optionally be specified
    in settings by setting a tuple of routes to ignore
    """
    def middleware(request):
        from django_otp import user_has_device
        if not user.is_verified() and not (if_configured and user.is_authenticated and not user_has_device(user)):
            return HttpResponseRedirect(settings.OTP_LOGIN_URL)
        return get_response(request)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • sorry not sure if i was clear enough, the middleware i had shown above actually works fine for my site, but i wanted to convert the decorator i showed into my middleware as i want users not not be able to view pages on my site unless they are otp required. If i use the decorator on pages then it workss restricting the users but i woudl rather have middleware instead of the decorator. Thanks – rob Nov 04 '21 at 21:33
  • @rob: but the middleware in the answer implements the logic of your decorator as a middleware class that you thus can add to the middleware classes. You should *not* inherit from the `MiddlewareMixin` baseclass: this has been from the `django.deprecation` module, and is only used to use *old-style* middleware for modern versions of Django but is *not* advised for new middleware. – Willem Van Onsem Nov 04 '21 at 21:38
  • @rob: I updated the middleware to work with the OTP as is specified in the decorator. – Willem Van Onsem Nov 04 '21 at 21:43