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:
Oliver 2023-07-12 16:29:08 +10:00 committed by GitHub
parent ee274739a6
commit 01f2aa5f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 11 deletions

View File

@ -13,6 +13,25 @@ from InvenTree.serializers import (InvenTreeImageSerializerField,
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):
"""Base serializer for a settings object."""
@ -30,6 +49,8 @@ class SettingsSerializer(InvenTreeModelSerializer):
api_url = serializers.CharField(read_only=True)
value = SettingsValueField()
def get_choices(self, obj):
"""Returns the choices available for a given item."""
results = []
@ -45,16 +66,6 @@ class SettingsSerializer(InvenTreeModelSerializer):
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):
"""Serializer for the InvenTreeSetting model."""

View File

@ -73,6 +73,12 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi
'description': 'Select a part object from the database',
'model': 'part.part',
},
'PROTECTED_SETTING': {
'name': 'Protected Setting',
'description': 'A protected setting, hidden from the UI',
'default': 'ABC-123',
'protected': True,
}
}
NAVIGATION = [

View File

@ -193,3 +193,76 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
with self.assertRaises(NotFound) as exc:
check_plugin(plugin_slug=None, plugin_pk='123')
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')

View File

@ -22,7 +22,9 @@
{{ setting.description }}
</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" %}
{% else %}
<div id='setting-{{ setting.pk }}'>