Skip to content

Commit aaaff0e

Browse files
akxpbadeer
andauthored
Potential bugfix for redirect_field_name AttributeError (#196)
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]>
1 parent d5a0220 commit aaaff0e

File tree

3 files changed

+42
-12
lines changed

3 files changed

+42
-12
lines changed

allauth_2fa/adapter.py

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from __future__ import annotations
22

3-
from urllib.parse import urlencode
4-
53
from allauth.account.adapter import DefaultAccountAdapter
64
from allauth.exceptions import ImmediateHttpResponse
75
from allauth.socialaccount.models import SocialLogin
@@ -11,6 +9,7 @@
119
from django.http import HttpResponseRedirect
1210
from django.urls import reverse
1311

12+
from allauth_2fa.utils import get_next_query_string
1413
from allauth_2fa.utils import user_has_valid_totp_device
1514

1615

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

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

5951
return redirect_url
6052

allauth_2fa/utils.py

+27
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from urllib.parse import urlencode
77

88
import qrcode
9+
from django.http import HttpRequest
910
from django_otp.models import Device
1011
from qrcode.image.svg import SvgPathImage
1112

@@ -35,3 +36,29 @@ def user_has_valid_totp_device(user) -> bool:
3536
if not user.is_authenticated:
3637
return False
3738
return user.totpdevice_set.filter(confirmed=True).exists()
39+
40+
41+
def get_next_query_string(request: HttpRequest) -> str | None:
42+
"""
43+
Get the query string (including the prefix `?`) to
44+
redirect to after a successful POST.
45+
46+
If a query string can't be determined, returns None.
47+
"""
48+
# If the view function smells like a class-based view,
49+
# we can interrogate it.
50+
try:
51+
view = request.resolver_match.func.view_class()
52+
redirect_field_name = view.redirect_field_name
53+
except AttributeError:
54+
# Interrogation failed :(
55+
return None
56+
57+
view.request = request
58+
query_params = request.GET.copy()
59+
success_url = view.get_success_url()
60+
if success_url:
61+
query_params[redirect_field_name] = success_url
62+
if query_params:
63+
return f"?{urlencode(query_params)}"
64+
return None

tests/test_allauth_2fa.py

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pytest
77
from allauth.account.signals import user_logged_in
8+
from allauth.account.views import PasswordResetFromKeyView
89
from django.conf import settings
910
from django.contrib.auth import get_user_model
1011
from django.contrib.auth.models import AbstractUser
@@ -20,6 +21,7 @@
2021
from pytest_django.asserts import assertRedirects
2122

2223
from allauth_2fa import views
24+
from allauth_2fa.adapter import OTPAdapter
2325
from allauth_2fa.middleware import BaseRequire2FAMiddleware
2426

2527
from . import forms as forms_overrides
@@ -393,3 +395,12 @@ def test_forms_override(
393395
settings_key: f"{custom_form_cls.__module__}.{custom_form_cls.__qualname__}",
394396
}
395397
assert view.get_form_class() is custom_form_cls
398+
399+
400+
@pytest.mark.parametrize("view_cls", [PasswordResetFromKeyView])
401+
def test_view_missing_attribute(request, view_cls) -> None:
402+
# Ensure we're testing a view that's missing the attribute.
403+
assert hasattr(view_cls(), "get_redirect_field_name") is False
404+
405+
# Ensure the function doesn't fail when the attribute is missing.
406+
assert OTPAdapter().get_2fa_authenticate_url(request) is not None

0 commit comments

Comments
 (0)