fix: users created via Admin Center never receive password reset emails (#12839)

* Users created via Admin Center never receive password reset emails
Fixes #12733

* fix issue when using registration endpoint

* remove duplicate code path; we now always do that

* remove old test backfill that is not required anymore
This commit is contained in:
Matthias Mair
2026-09-13 13:50:52 +10:00
committed by GitHub
parent 9181097194
commit 6b1a3b899e
4 changed files with 50 additions and 27 deletions
@@ -149,6 +149,7 @@ class RegistrationMixin:
def save_user(self, request, user, form, commit=True): def save_user(self, request, user, form, commit=True):
"""Check if a default group is set in settings.""" """Check if a default group is set in settings."""
user._is_registering = True # marker for email synchronization
# Create the user # Create the user
user = super().save_user(request, user, form) user = super().save_user(request, user, form)
@@ -3,8 +3,6 @@
from datetime import timedelta from datetime import timedelta
from unittest.mock import patch from unittest.mock import patch
from allauth.account.models import EmailAddress
import part.models import part.models
import part.tasks import part.tasks
import stock.models import stock.models
@@ -24,9 +22,6 @@ class StaleStockNotificationTests(InvenTreeTestCase):
"""Create test data as part of initialization.""" """Create test data as part of initialization."""
super().setUpTestData() super().setUpTestData()
# Add email address for user
EmailAddress.objects.create(user=cls.user, email='test@testing.com')
# Create test parts # Create test parts
cls.part1 = part.models.Part.objects.create( cls.part1 = part.models.Part.objects.create(
name='Test Part 1', name='Test Part 1',
+27 -22
View File
@@ -46,28 +46,6 @@ User.add_to_class('__str__', user_model_str) # Overriding User.__str__
# OVERRIDE END # OVERRIDE END
if settings.LDAP_AUTH:
from django_auth_ldap.backend import populate_user # ty: ignore[unresolved-import]
@receiver(populate_user)
def create_email_address(user, **kwargs):
"""If a django user is from LDAP and has an email attached to it, create an allauth email address for them automatically.
https://django-auth-ldap.readthedocs.io/en/latest/users.html#populating-users
https://django-auth-ldap.readthedocs.io/en/latest/reference.html#django_auth_ldap.backend.populate_user
"""
# User must exist in the database before we can create their EmailAddress. By their recommendation,
# we can just call .save() now
user.save()
# if they got an email address from LDAP, create it now and make it the primary
if (
user.email
and not EmailAddress.objects.filter(user=user, email=user.email).exists()
):
EmailAddress.objects.create(user=user, email=user.email, primary=True)
def default_token(): def default_token():
"""Generate a default value for the token.""" """Generate a default value for the token."""
return ApiToken.generate_key() return ApiToken.generate_key()
@@ -647,3 +625,30 @@ def validate_primary_group_on_group_change(sender, instance, action, **kwargs):
if profile.primary_group and profile.primary_group not in instance.groups.all(): if profile.primary_group and profile.primary_group not in instance.groups.all():
profile.primary_group = None profile.primary_group = None
profile.save() profile.save()
# update allauth user mail
@receiver(post_save, sender=User)
def sync_user_email_address(sender, instance: User, created: bool, **kwargs):
"""Keep the allauth EmailAddress in sync with User email field."""
# Are we currently in the API path of user registration?
if getattr(instance, '_is_registering', False):
return
if isImportingData() or isReadOnlyCommand():
return
if not instance.email:
return
primary_address = EmailAddress.objects.filter(user=instance, primary=True).first()
if primary_address:
if primary_address.email != instance.email:
primary_address.email = instance.email
primary_address.verified = False
primary_address.save()
elif not EmailAddress.objects.filter(user=instance, email=instance.email).exists():
EmailAddress.objects.create(
user=instance, email=instance.email, primary=True, verified=False
)
+22
View File
@@ -6,6 +6,8 @@ from unittest import mock
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.urls import reverse from django.urls import reverse
from allauth.account.models import EmailAddress
from InvenTree.unit_test import InvenTreeAPITestCase from InvenTree.unit_test import InvenTreeAPITestCase
from users.models import ApiToken from users.models import ApiToken
from users.ruleset import RULESET_NAMES, get_ruleset_models from users.ruleset import RULESET_NAMES, get_ruleset_models
@@ -370,6 +372,26 @@ class SuperuserAPITests(InvenTreeAPITestCase):
resp = self.put(url, {'password': 'inventree'}, expected_code=200) resp = self.put(url, {'password': 'inventree'}, expected_code=200)
self.assertEqual(resp.data, {}) self.assertEqual(resp.data, {})
def test_email_address_sync_signal(self):
"""Test emailadress sync."""
user = User.objects.create(username='start', email='start@example.org')
self.assertTrue(
EmailAddress.objects.filter(
user=user, email='start@example.org', primary=True
).exists()
)
# change should trigger emailaddress update
user.email = 'updated@example.org'
user.save()
self.assertFalse(
EmailAddress.objects.filter(user=user, email='start@example.org').exists()
)
updated = EmailAddress.objects.get(user=user, primary=True)
self.assertEqual(updated.email, 'updated@example.org')
self.assertFalse(updated.verified)
class UserTokenTests(InvenTreeAPITestCase): class UserTokenTests(InvenTreeAPITestCase):
"""Tests for user token functionality.""" """Tests for user token functionality."""