Преглед на файлове

fix(users): Copy default preferences for new users to prevent mutation

Copies DEFAULT_USER_PREFERENCES when creating UserConfig to prevent
later in-place updates from altering the shared reference. Adds
regression test for isolation between user configs and defaults.

Fixes #23281
Martin Hauser преди 2 дни
родител
ревизия
3a129b9b31
променени са 3 файла, в които са добавени 19 реда и са изтрити 6 реда
  1. 1 3
      netbox/core/tests/test_views.py
  2. 2 1
      netbox/users/signals.py
  3. 16 2
      netbox/users/tests/test_signals.py

+ 1 - 3
netbox/core/tests/test_views.py

@@ -194,9 +194,7 @@ class JobLogViewTestCase(TestCase):
 
     def setUp(self):
         super().setUp()
-        # UserConfig.set() mutates self.data in place, which can mutate DEFAULT_USER_PREFERENCES
-        # (the signal in users/signals.py initializes data with a shared reference). Assign a
-        # fresh literal instead. Pin per_page so page-boundary assertions don't depend on PAGINATE_COUNT.
+        # Pin per_page so page-boundary assertions don't depend on PAGINATE_COUNT.
         self.user.config.data = {'pagination': {'per_page': 50}}
         self.user.config.save()
 

+ 2 - 1
netbox/users/signals.py

@@ -1,3 +1,4 @@
+import copy
 import logging
 
 from django.contrib.auth.signals import user_logged_in, user_login_failed
@@ -42,4 +43,4 @@ def create_userconfig(instance, created, raw=False, **kwargs):
     """
     if created and not raw:
         config = get_config()
-        UserConfig(user=instance, data=config.DEFAULT_USER_PREFERENCES).save()
+        UserConfig(user=instance, data=copy.deepcopy(config.DEFAULT_USER_PREFERENCES)).save()

+ 16 - 2
netbox/users/tests/test_signals.py

@@ -1,7 +1,8 @@
 from django.contrib.auth.signals import user_logged_in, user_login_failed
 from django.db.models.signals import post_save
-from django.test import RequestFactory, TestCase, override_settings
+from django.test import RequestFactory, TestCase, override_settings, tag
 
+from netbox.config import get_config
 from users.models import User, UserConfig
 from users.signals import create_userconfig
 
@@ -60,7 +61,6 @@ class SetLanguageOnLoginSignalTestCase(TestCase):
         self.user = User.objects.create_user(username='alice', password='pw')
 
     def test_language_cookie_is_set_from_user_config(self):
-        # Assign a fresh dict to avoid mutating the shared DEFAULT_USER_PREFERENCES reference.
         self.user.config.data = {'locale': {'language': 'de'}}
         self.user.config.save()
         request = self.factory.post('/login/')
@@ -102,6 +102,20 @@ class CreateUserConfigSignalTestCase(TestCase):
         config = UserConfig.objects.get(user=user)
         self.assertEqual(config.data, {'pagination.per_page': 42})
 
+    @tag('regression')  # Ref: #23281
+    @override_settings(DEFAULT_USER_PREFERENCES={'locale': {'language': 'de'}, 'pagination': {'per_page': 25}})
+    def test_userconfig_changes_do_not_alter_default_preferences(self):
+        """Setting or clearing a new user's preferences changes neither the defaults nor later users."""
+        user = User.objects.create_user(username='alice', password='pw')
+        user.config.set('pagination.per_page', 50)
+        user.config.clear('locale.language')
+
+        later_user = User.objects.create_user(username='bob', password='pw')
+
+        defaults = {'locale': {'language': 'de'}, 'pagination': {'per_page': 25}}
+        self.assertEqual(get_config().DEFAULT_USER_PREFERENCES, defaults)
+        self.assertEqual(UserConfig.objects.get(user=later_user).data, defaults)
+
     def test_userconfig_is_not_created_for_existing_user(self):
         user = User.objects.create_user(username='alice', password='pw')
         UserConfig.objects.filter(user=user).delete()