Merge pull request #2885 from ryanpetrello/fix-2874

apply sensitive field filtering to /api/v2/hosts/?host_filter
This commit is contained in:
Ryan Petrello 2018-08-21 10:43:52 -04:00 committed by GitHub
commit 2acf055f6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 6 deletions

View File

@ -238,11 +238,11 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
app_label = 'main'
ordering = ('name',)
host_config_key = models.CharField(
host_config_key = prevent_search(models.CharField(
max_length=1024,
blank=True,
default='',
)
))
ask_diff_mode_on_launch = AskForField(
blank=True,
default=False,

View File

@ -2,7 +2,6 @@
# Python
import pytest
import mock
from collections import namedtuple
# AWX
from awx.main.utils.filters import SmartFilter, ExternalLoggerEnabled
@ -44,8 +43,26 @@ def test_log_configurable_severity(level, expect, dummy_log_record):
assert filter.filter(dummy_log_record) is expect
Field = namedtuple('Field', 'name')
Meta = namedtuple('Meta', 'fields')
class Field(object):
def __init__(self, name, related_model=None, __prevent_search__=None):
self.name = name
self.related_model = related_model
self.__prevent_search__ = __prevent_search__
class Meta(object):
def __init__(self, fields):
self._fields = {
f.name: f for f in fields
}
self.object_name = 'Host'
self.fields_map = {}
self.fields = self._fields.values()
def get_field(self, f):
return self._fields.get(f)
class mockObjects:
@ -53,15 +70,32 @@ class mockObjects:
return Q(*args, **kwargs)
class mockUser:
def __init__(self):
print("Host user created")
self._meta = Meta(fields=[
Field(name='password', __prevent_search__=True)
])
class mockHost:
def __init__(self):
print("Host mock created")
self.objects = mockObjects()
self._meta = Meta(fields=(Field(name='name'), Field(name='description')))
fields = [
Field(name='name'),
Field(name='description'),
Field(name='created_by', related_model=mockUser())
]
self._meta = Meta(fields=fields)
@mock.patch('awx.main.utils.filters.get_model', return_value=mockHost())
class TestSmartFilterQueryFromString():
@mock.patch(
'awx.api.filters.get_field_from_path',
lambda model, path: (model, path) # disable field filtering, because a__b isn't a real Host field
)
@pytest.mark.parametrize("filter_string,q_expected", [
('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})),
('"facts__facts__ space "="f"', Q(**{u"facts__facts__ space ": u"f"})),
@ -88,6 +122,16 @@ class TestSmartFilterQueryFromString():
SmartFilter.query_from_string(filter_string)
assert e.value.message == u"Invalid query " + filter_string
@pytest.mark.parametrize("filter_string", [
'created_by__password__icontains=pbkdf2'
'search=foo or created_by__password__icontains=pbkdf2',
'created_by__password__icontains=pbkdf2 or search=foo',
])
def test_forbidden_filter_string(self, mock_get_host_model, filter_string):
with pytest.raises(Exception) as e:
SmartFilter.query_from_string(filter_string)
"Filtering on password is not allowed." in str(e)
@pytest.mark.parametrize("filter_string,q_expected", [
(u'(a=abc\u1F5E3def)', Q(**{u"a": u"abc\u1F5E3def"})),
(u'(ansible_facts__a=abc\u1F5E3def)', Q(**{u"ansible_facts__contains": {u"a": u"abc\u1F5E3def"}})),

View File

@ -147,6 +147,10 @@ class SmartFilter(object):
q = reduce(lambda x, y: x | y, [models.Q(**{u'%s__icontains' % _k:_v}) for _k, _v in kwargs.items()])
self.result = Host.objects.filter(q)
else:
# detect loops and restrict access to sensitive fields
# this import is intentional here to avoid a circular import
from awx.api.filters import FieldLookupBackend
FieldLookupBackend().get_field_from_lookup(Host, k)
kwargs[k] = v
self.result = Host.objects.filter(**kwargs)