diff --git a/ChangeLog.rst b/ChangeLog.rst index 47806df736..fd3e9db5a8 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -12,6 +12,9 @@ Note worthy changes - Added MFA signals emitted when authenticators are added, removed or (in case of recovery codes) reset. +- Added a setting ``ACCOUNT_SEND_TO_UNKNOWN_EMAILS`` to disable sending of + emails to unknown accounts. + 0.58.2 (2023-11-06) ******************* diff --git a/allauth/account/app_settings.py b/allauth/account/app_settings.py index 86eea74c3a..68f84aaa25 100644 --- a/allauth/account/app_settings.py +++ b/allauth/account/app_settings.py @@ -386,6 +386,10 @@ def PASSWORD_RESET_TOKEN_GENERATOR(self): token_generator = EmailAwarePasswordResetTokenGenerator return token_generator + @property + def SEND_TO_UNKNOWN_EMAILS(self): + return self._setting("SEND_TO_UNKNOWN_EMAILS", True) + @property def REAUTHENTICATION_TIMEOUT(self): return self._setting("REAUTHENTICATION_TIMEOUT", 300) diff --git a/allauth/account/forms.py b/allauth/account/forms.py index dcc83ac6d8..2648710a60 100644 --- a/allauth/account/forms.py +++ b/allauth/account/forms.py @@ -582,7 +582,8 @@ def clean_email(self): def save(self, request, **kwargs): email = self.cleaned_data["email"] if not self.users: - self._send_unknown_account_mail(request, email) + if app_settings.SEND_TO_UNKNOWN_EMAILS: + self._send_unknown_account_mail(request, email) else: self._send_password_reset_mail(request, email, self.users, **kwargs) return email diff --git a/allauth/account/tests/test_reset_password.py b/allauth/account/tests/test_reset_password.py index 8e7c48450e..34a63b545c 100644 --- a/allauth/account/tests/test_reset_password.py +++ b/allauth/account/tests/test_reset_password.py @@ -88,6 +88,28 @@ def test_password_forgotten_no_username_hint(self): body = mail.outbox[0].body assert user.username not in body + @override_settings( + ACCOUNT_PREVENT_ENUMERATION=True, + ) + def test_reset_password_unknown_account(self): + self.client.post( + reverse("account_reset_password"), + data={"email": "unknown@example.org"}, + ) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ["unknown@example.org"]) + + @override_settings( + ACCOUNT_PREVENT_ENUMERATION=True, + ACCOUNT_SEND_TO_UNKNOWN_EMAILS=False, + ) + def test_reset_password_unknown_account_disabled(self): + self.client.post( + reverse("account_reset_password"), + data={"email": "unknown@example.org"}, + ) + self.assertEqual(len(mail.outbox), 0) + def _request_new_password(self): user = get_user_model().objects.create( username="john", email="john@example.org", is_active=True diff --git a/allauth/account/views.py b/allauth/account/views.py index 83e48b8e39..62081e0cf4 100644 --- a/allauth/account/views.py +++ b/allauth/account/views.py @@ -776,6 +776,17 @@ class PasswordResetView(AjaxCapableProcessFormViewMixin, FormView): def get_form_class(self): return get_form_class(app_settings.FORMS, "reset_password", self.form_class) + def post(self, request, *args, **kwargs): + """ + Handle POST requests: instantiate a form instance with the passed + POST variables and then check if it's valid. + """ + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + def form_valid(self, form): r429 = ratelimit.consume_or_429( self.request,