mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Protected settings fix (#5229)
* Hide protected setting in settings view * Implement custom serializer for setting value - Return '***' if the setting is protected * Implement to_internal_value * Stringify * Add protected setting to sample plugin * Unit tests for plugin settings API * Update unit test
This commit is contained in:
		| @@ -13,6 +13,25 @@ from InvenTree.serializers import (InvenTreeImageSerializerField, | |||||||
|                                    InvenTreeModelSerializer) |                                    InvenTreeModelSerializer) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class SettingsValueField(serializers.Field): | ||||||
|  |     """Custom serializer field for a settings value.""" | ||||||
|  |  | ||||||
|  |     def get_attribute(self, instance): | ||||||
|  |         """Return the object instance, not the attribute value.""" | ||||||
|  |         return instance | ||||||
|  |  | ||||||
|  |     def to_representation(self, instance): | ||||||
|  |         """Return the value of the setting: | ||||||
|  |  | ||||||
|  |         - Protected settings are returned as '***' | ||||||
|  |         """ | ||||||
|  |         return '***' if instance.protected else str(instance.value) | ||||||
|  |  | ||||||
|  |     def to_internal_value(self, data): | ||||||
|  |         """Return the internal value of the setting""" | ||||||
|  |         return str(data) | ||||||
|  |  | ||||||
|  |  | ||||||
| class SettingsSerializer(InvenTreeModelSerializer): | class SettingsSerializer(InvenTreeModelSerializer): | ||||||
|     """Base serializer for a settings object.""" |     """Base serializer for a settings object.""" | ||||||
|  |  | ||||||
| @@ -30,6 +49,8 @@ class SettingsSerializer(InvenTreeModelSerializer): | |||||||
|  |  | ||||||
|     api_url = serializers.CharField(read_only=True) |     api_url = serializers.CharField(read_only=True) | ||||||
|  |  | ||||||
|  |     value = SettingsValueField() | ||||||
|  |  | ||||||
|     def get_choices(self, obj): |     def get_choices(self, obj): | ||||||
|         """Returns the choices available for a given item.""" |         """Returns the choices available for a given item.""" | ||||||
|         results = [] |         results = [] | ||||||
| @@ -45,16 +66,6 @@ class SettingsSerializer(InvenTreeModelSerializer): | |||||||
|  |  | ||||||
|         return results |         return results | ||||||
|  |  | ||||||
|     def get_value(self, obj): |  | ||||||
|         """Make sure protected values are not returned.""" |  | ||||||
|         # never return protected values |  | ||||||
|         if obj.protected: |  | ||||||
|             result = '***' |  | ||||||
|         else: |  | ||||||
|             result = obj.value |  | ||||||
|  |  | ||||||
|         return result |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class GlobalSettingsSerializer(SettingsSerializer): | class GlobalSettingsSerializer(SettingsSerializer): | ||||||
|     """Serializer for the InvenTreeSetting model.""" |     """Serializer for the InvenTreeSetting model.""" | ||||||
|   | |||||||
| @@ -73,6 +73,12 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi | |||||||
|             'description': 'Select a part object from the database', |             'description': 'Select a part object from the database', | ||||||
|             'model': 'part.part', |             'model': 'part.part', | ||||||
|         }, |         }, | ||||||
|  |         'PROTECTED_SETTING': { | ||||||
|  |             'name': 'Protected Setting', | ||||||
|  |             'description': 'A protected setting, hidden from the UI', | ||||||
|  |             'default': 'ABC-123', | ||||||
|  |             'protected': True, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     NAVIGATION = [ |     NAVIGATION = [ | ||||||
|   | |||||||
| @@ -193,3 +193,76 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase): | |||||||
|         with self.assertRaises(NotFound) as exc: |         with self.assertRaises(NotFound) as exc: | ||||||
|             check_plugin(plugin_slug=None, plugin_pk='123') |             check_plugin(plugin_slug=None, plugin_pk='123') | ||||||
|         self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed") |         self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed") | ||||||
|  |  | ||||||
|  |     def test_plugin_settings(self): | ||||||
|  |         """Test plugin settings access via the API""" | ||||||
|  |  | ||||||
|  |         # Ensure we have superuser permissions | ||||||
|  |         self.user.is_superuser = True | ||||||
|  |         self.user.save() | ||||||
|  |  | ||||||
|  |         # Activate the 'sample' plugin via the API | ||||||
|  |         cfg = PluginConfig.objects.filter(key='sample').first() | ||||||
|  |         url = reverse('api-plugin-detail-activate', kwargs={'pk': cfg.pk}) | ||||||
|  |         self.client.patch(url, {}, expected_code=200) | ||||||
|  |  | ||||||
|  |         # Valid plugin settings endpoints | ||||||
|  |         valid_settings = [ | ||||||
|  |             'SELECT_PART', | ||||||
|  |             'API_KEY', | ||||||
|  |             'NUMERICAL_SETTING', | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         for key in valid_settings: | ||||||
|  |             response = self.get( | ||||||
|  |                 reverse('api-plugin-setting-detail', kwargs={ | ||||||
|  |                     'plugin': 'sample', | ||||||
|  |                     'key': key | ||||||
|  |                 })) | ||||||
|  |  | ||||||
|  |             self.assertEqual(response.data['key'], key) | ||||||
|  |  | ||||||
|  |         # Test that an invalid setting key raises a 404 error | ||||||
|  |         response = self.get( | ||||||
|  |             reverse('api-plugin-setting-detail', kwargs={ | ||||||
|  |                 'plugin': 'sample', | ||||||
|  |                 'key': 'INVALID_SETTING' | ||||||
|  |             }), | ||||||
|  |             expected_code=404 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Test that a protected setting returns hidden value | ||||||
|  |         response = self.get( | ||||||
|  |             reverse('api-plugin-setting-detail', kwargs={ | ||||||
|  |                 'plugin': 'sample', | ||||||
|  |                 'key': 'PROTECTED_SETTING' | ||||||
|  |             }), | ||||||
|  |             expected_code=200 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.assertEqual(response.data['value'], '***') | ||||||
|  |  | ||||||
|  |         # Test that we can update a setting value | ||||||
|  |         response = self.patch( | ||||||
|  |             reverse('api-plugin-setting-detail', kwargs={ | ||||||
|  |                 'plugin': 'sample', | ||||||
|  |                 'key': 'NUMERICAL_SETTING' | ||||||
|  |             }), | ||||||
|  |             { | ||||||
|  |                 'value': 456 | ||||||
|  |             }, | ||||||
|  |             expected_code=200 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.assertEqual(response.data['value'], '456') | ||||||
|  |  | ||||||
|  |         # Retrieve the value again | ||||||
|  |         response = self.get( | ||||||
|  |             reverse('api-plugin-setting-detail', kwargs={ | ||||||
|  |                 'plugin': 'sample', | ||||||
|  |                 'key': 'NUMERICAL_SETTING' | ||||||
|  |             }), | ||||||
|  |             expected_code=200 | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         self.assertEqual(response.data['value'], '456') | ||||||
|   | |||||||
| @@ -22,7 +22,9 @@ | |||||||
|         {{ setting.description }} |         {{ setting.description }} | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|         {% if setting.is_bool %} |         {% if setting.protected %} | ||||||
|  |         <span style='color: red;'>***</span> <span class='fas fa-lock icon-red'></span> | ||||||
|  |         {% elif setting.is_bool %} | ||||||
|         {% include "InvenTree/settings/setting_boolean.html" %} |         {% include "InvenTree/settings/setting_boolean.html" %} | ||||||
|         {% else %} |         {% else %} | ||||||
|         <div id='setting-{{ setting.pk }}'> |         <div id='setting-{{ setting.pk }}'> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user