Skip to content

Commit

Permalink
Merge pull request #737 from claudep/class_deco
Browse files Browse the repository at this point in the history
Use Django's method_decorator instead of custom utility
  • Loading branch information
moggers87 authored Aug 10, 2024
2 parents e9fe6a7 + 856d8a1 commit 68ef964
Show file tree
Hide file tree
Showing 8 changed files with 25 additions and 49 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

### Removed
- Dropped support for Django <4.2.
- Removed custom `utils.class_view_decorator()` in favor of Django's
`method_decorator()`.

## 1.16.0
### Fixed
Expand Down
8 changes: 5 additions & 3 deletions docs/class-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ Decorators
.. automodule:: django_otp.decorators
:members:

.. automodule:: two_factor.views.utils
:members: class_view_decorator

Models
------
.. autoclass:: two_factor.plugins.phonenumber.models.PhoneDevice
Expand Down Expand Up @@ -51,6 +48,11 @@ Template Tags
.. automodule:: two_factor.plugins.phonenumber.templatetags.phonenumber
:members:

Utilities
---------
.. automodule:: two_factor.views.utils
:members:

Views
-----
.. autoclass:: two_factor.views.LoginView
Expand Down
4 changes: 2 additions & 2 deletions example/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from django.conf import settings
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import redirect, resolve_url
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, TemplateView

from two_factor.views import OTPRequiredMixin
from two_factor.views.utils import class_view_decorator


class HomeView(TemplateView):
Expand All @@ -30,6 +30,6 @@ def get_context_data(self, **kwargs):
return context


@class_view_decorator(never_cache)
@method_decorator(never_cache, name='dispatch')
class ExampleSecretView(OTPRequiredMixin, TemplateView):
template_name = 'secret.html'
5 changes: 2 additions & 3 deletions two_factor/gateways/twilio/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@
from django.contrib.sites.shortcuts import get_current_site
from django.template.response import TemplateResponse
from django.utils import translation
from django.utils.decorators import method_decorator
from django.utils.translation import check_for_language, pgettext
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View

from ...views.utils import class_view_decorator
from .gateway import validate_voice_locale


@class_view_decorator(never_cache)
@class_view_decorator(csrf_exempt)
@method_decorator([never_cache, csrf_exempt], name='dispatch')
class TwilioCallApp(View):
"""
View used by Twilio for the interactive token verification by phone.
Expand Down
11 changes: 4 additions & 7 deletions two_factor/plugins/phonenumber/views.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
from django.conf import settings
from django.shortcuts import redirect, resolve_url
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.generic import DeleteView
from django_otp.decorators import otp_required
from django_otp.util import random_hex

from two_factor.forms import DeviceValidationForm
from two_factor.views.utils import (
IdempotentSessionWizardView, class_view_decorator,
)
from two_factor.views.utils import IdempotentSessionWizardView

from .forms import PhoneNumberMethodForm
from .models import PhoneDevice
from .utils import get_available_phone_methods


@class_view_decorator(never_cache)
@class_view_decorator(otp_required)
@method_decorator([never_cache, otp_required], name='dispatch')
class PhoneSetupView(IdempotentSessionWizardView):
"""
View for configuring a phone number for receiving tokens.
Expand Down Expand Up @@ -87,8 +85,7 @@ def get_context_data(self, form, **kwargs):
return super().get_context_data(form, **kwargs)


@class_view_decorator(never_cache)
@class_view_decorator(otp_required)
@method_decorator([never_cache, otp_required], name='dispatch')
class PhoneDeleteView(DeleteView):
"""
View for removing a phone number used for verification.
Expand Down
20 changes: 7 additions & 13 deletions two_factor/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@
)
from ..utils import default_device, get_otpauth_url
from .utils import (
IdempotentSessionWizardView, class_view_decorator,
get_remember_device_cookie, validate_remember_device_cookie,
IdempotentSessionWizardView, get_remember_device_cookie,
validate_remember_device_cookie,
)

logger = logging.getLogger(__name__)

REMEMBER_COOKIE_PREFIX = getattr(settings, 'TWO_FACTOR_REMEMBER_COOKIE_PREFIX', 'remember-cookie_')


@method_decorator([sensitive_post_parameters(), csrf_protect, never_cache], name='dispatch')
class LoginView(RedirectURLMixin, IdempotentSessionWizardView):
"""
View for handling the login process, including OTP verification.
Expand Down Expand Up @@ -405,9 +406,6 @@ def delete_cookies_from_response(self, response):

# Copied from django.contrib.auth.views.LoginView (Branch: stable/1.11.x)
# https://github.com/django/django/blob/58df8aa40fe88f753ba79e091a52f236246260b3/django/contrib/auth/views.py#L49
@method_decorator(sensitive_post_parameters())
@method_decorator(csrf_protect)
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
if self.redirect_authenticated_user and self.request.user.is_authenticated:
redirect_to = self.get_success_url()
Expand All @@ -420,8 +418,7 @@ def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)


@class_view_decorator(never_cache)
@class_view_decorator(login_required)
@method_decorator([never_cache, login_required], name='dispatch')
class SetupView(RedirectURLMixin, IdempotentSessionWizardView):
"""
View for handling OTP setup using a wizard.
Expand Down Expand Up @@ -628,8 +625,7 @@ def get_form_metadata(self, step):
return self.storage.extra_data['forms'].get(step, None)


@class_view_decorator(never_cache)
@class_view_decorator(otp_required)
@method_decorator([never_cache, otp_required], name='dispatch')
class BackupTokensView(FormView):
"""
View for listing and generating backup tokens.
Expand Down Expand Up @@ -664,8 +660,7 @@ def form_valid(self, form):
return redirect(self.success_url)


@class_view_decorator(never_cache)
@class_view_decorator(otp_required)
@method_decorator([never_cache, otp_required], name='dispatch')
class SetupCompleteView(TemplateView):
"""
View congratulation the user when OTP setup has completed.
Expand All @@ -683,8 +678,7 @@ def get_context_data(self):
}


@class_view_decorator(never_cache)
@class_view_decorator(login_required)
@method_decorator([never_cache, login_required], name='dispatch')
class QRGeneratorView(View):
"""
View returns an SVG image with the OTP token information
Expand Down
7 changes: 3 additions & 4 deletions two_factor/views/profile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, resolve_url
from django.utils.decorators import method_decorator
from django.utils.functional import lazy
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, TemplateView
Expand All @@ -13,11 +14,9 @@

from ..forms import DisableForm
from ..utils import default_device
from .utils import class_view_decorator


@class_view_decorator(never_cache)
@class_view_decorator(login_required)
@method_decorator([never_cache, login_required], name='dispatch')
class ProfileView(TemplateView):
"""
View used by users for managing two-factor configuration.
Expand Down Expand Up @@ -48,7 +47,7 @@ def get_context_data(self, **kwargs):
return context


@class_view_decorator(never_cache)
@method_decorator(never_cache, name='dispatch')
class DisableView(FormView):
"""
View for disabling two-factor for a user's account.
Expand Down
17 changes: 0 additions & 17 deletions two_factor/views/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
BadSignature, SignatureExpired, b62_decode, b62_encode,
)
from django.utils.crypto import salted_hmac
from django.utils.decorators import method_decorator
from django.utils.encoding import force_bytes
from django.utils.translation import gettext as _
from formtools.wizard.forms import ManagementForm
Expand Down Expand Up @@ -218,22 +217,6 @@ def render_done(self, form, **kwargs):
return done_response


def class_view_decorator(function_decorator):
"""
Converts a function based decorator into a class based decorator usable
on class based Views.
Can't subclass the `View` as it breaks inheritance (super in particular),
so we monkey-patch instead.
From: http://stackoverflow.com/a/8429311/58107
"""
def simple_decorator(View):
View.dispatch = method_decorator(function_decorator)(View.dispatch)
return View
return simple_decorator


remember_device_cookie_separator = ':'


Expand Down

0 comments on commit 68ef964

Please sign in to comment.