Skip to content

Commit

Permalink
Potential bugfix for redirect_field_name AttributeError (#196)
Browse files Browse the repository at this point in the history
Getting this error when submitting the change password form after opening a key-based password reset link.

AttributeError: 'PasswordResetFromKeyView' object has no attribute 'redirect_field_name'

Co-authored-by: Preston Badeer <[email protected]>
  • Loading branch information
akx and pbadeer authored Sep 24, 2024
1 parent d5a0220 commit aaaff0e
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 12 deletions.
16 changes: 4 additions & 12 deletions allauth_2fa/adapter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import annotations

from urllib.parse import urlencode

from allauth.account.adapter import DefaultAccountAdapter
from allauth.exceptions import ImmediateHttpResponse
from allauth.socialaccount.models import SocialLogin
Expand All @@ -11,6 +9,7 @@
from django.http import HttpResponseRedirect
from django.urls import reverse

from allauth_2fa.utils import get_next_query_string
from allauth_2fa.utils import user_has_valid_totp_device


Expand Down Expand Up @@ -45,16 +44,9 @@ def get_2fa_authenticate_url(self, request: HttpRequest) -> str:
redirect_url = reverse("two-factor-authenticate")

# Add "next" parameter to the URL if possible.
# If the view function smells like a class-based view, we can interrogate it.
if getattr(request.resolver_match.func, "view_class", None):
view = request.resolver_match.func.view_class()
view.request = request
success_url = view.get_success_url()
query_params = request.GET.copy()
if success_url:
query_params[view.redirect_field_name] = success_url
if query_params:
redirect_url += f"?{urlencode(query_params)}"
query_string = get_next_query_string(request)
if query_string:
redirect_url += query_string

return redirect_url

Expand Down
27 changes: 27 additions & 0 deletions allauth_2fa/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from urllib.parse import urlencode

import qrcode
from django.http import HttpRequest
from django_otp.models import Device
from qrcode.image.svg import SvgPathImage

Expand Down Expand Up @@ -35,3 +36,29 @@ def user_has_valid_totp_device(user) -> bool:
if not user.is_authenticated:
return False
return user.totpdevice_set.filter(confirmed=True).exists()


def get_next_query_string(request: HttpRequest) -> str | None:
"""
Get the query string (including the prefix `?`) to
redirect to after a successful POST.
If a query string can't be determined, returns None.
"""
# If the view function smells like a class-based view,
# we can interrogate it.
try:
view = request.resolver_match.func.view_class()
redirect_field_name = view.redirect_field_name
except AttributeError:
# Interrogation failed :(
return None

view.request = request
query_params = request.GET.copy()
success_url = view.get_success_url()
if success_url:
query_params[redirect_field_name] = success_url
if query_params:
return f"?{urlencode(query_params)}"
return None
11 changes: 11 additions & 0 deletions tests/test_allauth_2fa.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pytest
from allauth.account.signals import user_logged_in
from allauth.account.views import PasswordResetFromKeyView
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AbstractUser
Expand All @@ -20,6 +21,7 @@
from pytest_django.asserts import assertRedirects

from allauth_2fa import views
from allauth_2fa.adapter import OTPAdapter
from allauth_2fa.middleware import BaseRequire2FAMiddleware

from . import forms as forms_overrides
Expand Down Expand Up @@ -393,3 +395,12 @@ def test_forms_override(
settings_key: f"{custom_form_cls.__module__}.{custom_form_cls.__qualname__}",
}
assert view.get_form_class() is custom_form_cls


@pytest.mark.parametrize("view_cls", [PasswordResetFromKeyView])
def test_view_missing_attribute(request, view_cls) -> None:
# Ensure we're testing a view that's missing the attribute.
assert hasattr(view_cls(), "get_redirect_field_name") is False

# Ensure the function doesn't fail when the attribute is missing.
assert OTPAdapter().get_2fa_authenticate_url(request) is not None

0 comments on commit aaaff0e

Please sign in to comment.