From 758ad164fe74e8496991de5888e17e19c2a1b38a Mon Sep 17 00:00:00 2001 From: Jeff Bradberry Date: Tue, 18 Jun 2019 16:10:38 -0400 Subject: [PATCH] Include defined fields from all parent classes of a HybridDictField since our settings registry adds a mixin class when doing validation on these. related #4099 --- awx/conf/tests/functional/test_api.py | 29 +++++++++++++++++++++++++-- awx/sso/fields.py | 12 ++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/awx/conf/tests/functional/test_api.py b/awx/conf/tests/functional/test_api.py index aac1a56127..d7bb06a1bf 100644 --- a/awx/conf/tests/functional/test_api.py +++ b/awx/conf/tests/functional/test_api.py @@ -8,6 +8,7 @@ from awx.main.utils.encryption import decrypt_field from awx.conf import fields from awx.conf.registry import settings_registry from awx.conf.models import Setting +from awx.sso import fields as sso_fields @pytest.fixture @@ -137,7 +138,7 @@ def test_setting_signleton_retrieve_hierachy(api_request, dummy_setting): @pytest.mark.django_db -def test_setting_signleton_retrieve_readonly(api_request, dummy_setting): +def test_setting_singleton_retrieve_readonly(api_request, dummy_setting): with dummy_setting( 'FOO_BAR', field_class=fields.IntegerField, @@ -183,6 +184,30 @@ def test_setting_singleton_update(api_request, dummy_setting): assert response.data['FOO_BAR'] == 4 +@pytest.mark.django_db +def test_setting_singleton_update_hybriddictfield_with_forbidden(api_request, dummy_setting): + # Some HybridDictField subclasses have a child of _Forbidden, + # indicating that only the defined fields can be filled in. Make + # sure that the _Forbidden validator doesn't get used for the + # fields. See also https://github.com/ansible/awx/issues/4099. + with dummy_setting( + 'FOO_BAR', + field_class=sso_fields.SAMLOrgAttrField, + category='FooBar', + category_slug='foobar', + ), mock.patch('awx.conf.views.handle_setting_changes'): + api_request( + 'patch', + reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}), + data={'FOO_BAR': {'saml_admin_attr': 'Admins', 'saml_attr': 'Orgs'}} + ) + response = api_request( + 'get', + reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}) + ) + assert response.data['FOO_BAR'] == {'saml_admin_attr': 'Admins', 'saml_attr': 'Orgs'} + + @pytest.mark.django_db def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy_setting): with dummy_setting( @@ -206,7 +231,7 @@ def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy @pytest.mark.django_db -def test_setting_singleton_update_dont_change_encripted_mark(api_request, dummy_setting): +def test_setting_singleton_update_dont_change_encrypted_mark(api_request, dummy_setting): with dummy_setting( 'FOO_BAR', field_class=fields.CharField, diff --git a/awx/sso/fields.py b/awx/sso/fields.py index 09b87fff02..57198bf082 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -93,12 +93,14 @@ class HybridDictField(fields.DictField): self.allow_blank = kwargs.pop('allow_blank', False) fields = [ - (field_name, obj) - for field_name, obj in self.__class__.__dict__.items() - if isinstance(obj, Field) and field_name != 'child' + sorted( + ((field_name, obj) for field_name, obj in cls.__dict__.items() + if isinstance(obj, Field) and field_name != 'child'), + key=lambda x: x[1]._creation_counter + ) + for cls in reversed(self.__class__.__mro__) ] - fields.sort(key=lambda x: x[1]._creation_counter) - self._declared_fields = collections.OrderedDict(fields) + self._declared_fields = collections.OrderedDict(f for group in fields for f in group) super().__init__(*args, **kwargs)