1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 03:36:48 +00:00

fix(accounts): an email address belongs to one user, so "Lost OTP?" reaches it

"Lost OTP?" mails the QR code only when exactly one active user has the
address. Any user could enter another user's address in their profile and
silently switch the recovery off for both. The profile and the admin user
forms now refuse an address another user has, ignoring case; an empty
address stays allowed for everyone.
This commit is contained in:
catborise 2026-10-26 10:51:05 +03:00
parent 5d8b7e3f61
commit 2e5ef34473
3 changed files with 45 additions and 2 deletions

View file

@ -34,7 +34,18 @@ class UserInstanceForm(ModelForm):
fields = "__all__"
class ProfileForm(ModelForm):
class UniqueEmailMixin:
""""Lost OTP?" mails only an address that is one user's: a second user
with the same address would switch it off for both."""
def clean_email(self):
email = self.cleaned_data["email"]
if email and get_user_model().objects.filter(email__iexact=email).exclude(pk=self.instance.pk).exists():
raise ValidationError(_("Another user has this email address"))
return email
class ProfileForm(UniqueEmailMixin, ModelForm):
class Meta:
model = get_user_model()
fields = ("first_name", "last_name", "email")

View file

@ -0,0 +1,31 @@
"""An email address belongs to one user: "Lost OTP?" mails only an exact,
single match, so a second user with the same address would switch it off for both."""
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from accounts.forms import ProfileForm
from admin.forms import UserForm
class UniqueEmailTests(TestCase):
def setUp(self):
self.alice = User.objects.create_user("alice", "alice@example.com", "pw")
self.bob = User.objects.create_user("bob", "", "pw")
def test_the_profile_refuses_another_users_address(self):
self.client.force_login(self.bob)
response = self.client.post(reverse("accounts:profile"), {"email": "Alice@Example.com"})
self.assertContains(response, "Another user has this email address")
self.bob.refresh_from_db()
self.assertEqual(self.bob.email, "")
def test_the_admin_form_refuses_another_users_address(self):
form = UserForm({"username": "bob", "email": "alice@example.com", "is_active": "on"}, instance=self.bob)
self.assertEqual(form.errors["email"], ["Another user has this email address"])
def test_own_address_and_no_address_are_kept(self):
User.objects.create_user("carol", "", "pw")
self.assertTrue(ProfileForm({"email": ""}, instance=self.bob).is_valid())
self.assertTrue(ProfileForm({"email": "alice@example.com"}, instance=self.alice).is_valid())

View file

@ -5,6 +5,7 @@ from django.contrib.auth.models import Group, User
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from accounts.forms import UniqueEmailMixin
from accounts.models import UserAttributes
from .models import Permission
@ -42,7 +43,7 @@ class GroupForm(forms.ModelForm):
fields = "__all__"
class UserForm(forms.ModelForm):
class UserForm(UniqueEmailMixin, forms.ModelForm):
user_permissions = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
queryset=Permission.objects.filter(content_type__model="permissionset"),