From f4d4c43d94b7e0b60276df21ff7d0856cec8c840 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Thu, 23 Feb 2017 15:22:08 -0500 Subject: [PATCH] prohibit `order_by=` for sensitive fields see: #5526 --- awx/api/filters.py | 15 +++++++++++++++ awx/main/tests/functional/api/test_credential.py | 15 +++++++++++++++ awx/main/tests/functional/api/test_inventory.py | 15 +++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/awx/api/filters.py b/awx/api/filters.py index 41bbc3bba8..fbd7a0ba64 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -311,6 +311,8 @@ class OrderByBackend(BaseFilterBackend): else: order_by = (value,) if order_by: + order_by = self._strip_sensitive_model_fields(queryset.model, order_by) + # Special handling of the type field for ordering. In this # case, we're not sorting exactly on the type field, but # given the limited number of views with multiple types, @@ -333,3 +335,16 @@ class OrderByBackend(BaseFilterBackend): except FieldError as e: # Return a 400 for invalid field names. raise ParseError(*e.args) + + def _strip_sensitive_model_fields(self, model, order_by): + for field_name in order_by: + # strip off the negation prefix `-` if it exists + field_name = field_name.split('-')[-1] + try: + # if the field name is encrypted/sensitive, don't sort on it + if field_name in getattr(model, 'PASSWORD_FIELDS', ()) or \ + getattr(model._meta.get_field(field_name), '__prevent_search__', False): + raise ParseError(_('cannot order by field %s') % field_name) + except FieldDoesNotExist: + pass + yield field_name diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index 8f596cdac9..458a629cce 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -339,6 +339,21 @@ def test_list_created_org_credentials(post, get, organization, org_admin, org_me assert response.data['count'] == 0 +@pytest.mark.parametrize('order_by', ('password', '-password', 'password,pk', '-password,pk')) +@pytest.mark.django_db +def test_list_cannot_order_by_encrypted_field(post, get, organization, org_admin, order_by): + for i, password in enumerate(('abc', 'def', 'xyz')): + response = post(reverse('api:credential_list'), { + 'organization': organization.id, + 'name': 'C%d' % i, + 'password': password + }, org_admin) + + response = get(reverse('api:credential_list'), org_admin, + QUERY_STRING='order_by=%s' % order_by, status=400) + assert response.status_code == 400 + + # # Openstack Credentials # diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index 925d7352fd..839cffab9e 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -35,6 +35,21 @@ def test_edit_inventory(put, inventory, alice, role_field, expected_status_code) put(reverse('api:inventory_detail', args=(inventory.id,)), data, alice, expect=expected_status_code) +@pytest.mark.parametrize('order_by', ('script', '-script', 'script,pk', '-script,pk')) +@pytest.mark.django_db +def test_list_cannot_order_by_unsearchable_field(get, organization, alice, order_by): + for i, script in enumerate(('#!/bin/a', '#!/bin/b', '#!/bin/c')): + custom_script = organization.custom_inventory_scripts.create( + name="I%d" % i, + script=script + ) + custom_script.admin_role.members.add(alice) + + response = get(reverse('api:inventory_script_list'), alice, + QUERY_STRING='order_by=%s' % order_by, status=400) + assert response.status_code == 400 + + @pytest.mark.parametrize("role_field,expected_status_code", [ (None, 403), ('admin_role', 201),