diff --git a/geonode/base/api/filters.py b/geonode/base/api/filters.py index 15b4607bf23..da20dd21f50 100644 --- a/geonode/base/api/filters.py +++ b/geonode/base/api/filters.py @@ -33,8 +33,10 @@ class DynamicSearchFilter(SearchFilter): + # search_fields from the request are restricted to the view's allow-list def get_search_fields(self, view, request): - return request.GET.getlist("search_fields", []) + allowed = getattr(view, "search_fields", None) or [] + return [f for f in request.GET.getlist("search_fields", []) if f in allowed] class ExtentFilter(BaseFilterBackend): diff --git a/geonode/base/api/tests.py b/geonode/base/api/tests.py index e6b379889a2..ea8eed8bb27 100644 --- a/geonode/base/api/tests.py +++ b/geonode/base/api/tests.py @@ -871,6 +871,44 @@ def test_search_resources(self): # Pagination self.assertEqual(len(response.data["resources"]), 1) + def test_search_fields_allow_list(self): + """ + search_fields not allowed by the view are ignored. + """ + term = "no-such-value-b3wf" + + def _check(url, allowed, rejected): + total = self.client.get(url, format="json").data["total"] + self.assertGreater(total, 0) + for field in allowed: + response = self.client.get(f"{url}?search={term}&search_fields={field}", format="json") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["total"], 0, field) + for field in rejected: + response = self.client.get(f"{url}?search={term}&search_fields={field}", format="json") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["total"], total, field) + + resource_rejected = ["owner__password__startswith", "owner__email__regex", "owner__username", "uuid", "^title"] + # anonymous + for name in ("base-resources-list", "datasets-list", "maps-list", "documents-list"): + _check(reverse(name), allowed=["title", "abstract"], rejected=resource_rejected) + + admin = get_user_model().objects.get(username="admin") + GeoApp.objects.create(title="search fields geoapp", owner=admin) + self.assertTrue(self.client.login(username="admin", password="admin")) + _check(reverse("geoapps-list"), allowed=["title", "abstract"], rejected=resource_rejected) + _check( + reverse("users-list"), + allowed=["username", "first_name", "last_name"], + rejected=["password__startswith", "email", "username__regex"], + ) + _check( + reverse("group-profiles-list"), + allowed=["title", "slug"], + rejected=["group__user__password__startswith", "description"], + ) + def test_filter_resources(self): """ Ensure we can filter across the Resource Base list. diff --git a/geonode/base/api/views.py b/geonode/base/api/views.py index 4f7078a94cf..f0d0af7944f 100644 --- a/geonode/base/api/views.py +++ b/geonode/base/api/views.py @@ -127,6 +127,7 @@ class GroupViewSet(DynamicModelViewSet): IsManagerEditOrAdmin, ] filter_backends = [DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter] + search_fields = ["title", "slug"] serializer_class = GroupProfileSerializer pagination_class = GeoNodeApiPagination @@ -307,6 +308,7 @@ class ResourceBaseViewSet(ApiPresetsInitializer, MultiLangViewMixin, DeprecatedE FavoriteFilter, ] queryset = ResourceBase.objects.select_related("owner").order_by("-created") + search_fields = ["title", "abstract"] serializer_class = ResourceBaseSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/documents/api/views.py b/geonode/documents/api/views.py index fa8d7739bab..c42bfaa444a 100644 --- a/geonode/documents/api/views.py +++ b/geonode/documents/api/views.py @@ -63,6 +63,7 @@ class DocumentViewSet(ApiPresetsInitializer, MultiLangViewMixin, DynamicModelVie DocumentPermissionsFilter, ] queryset = Document.objects.all().order_by("-created") + search_fields = ["title", "abstract"] serializer_class = DocumentSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/geoapps/api/views.py b/geonode/geoapps/api/views.py index e4094b11848..2bf1eb9fa2f 100644 --- a/geonode/geoapps/api/views.py +++ b/geonode/geoapps/api/views.py @@ -57,6 +57,7 @@ class GeoAppViewSet(ApiPresetsInitializer, MultiLangViewMixin, DynamicModelViewS GeoAppPermissionsFilter, ] queryset = GeoApp.objects.all().order_by("-created") + search_fields = ["title", "abstract"] serializer_class = GeoAppSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/layers/api/views.py b/geonode/layers/api/views.py index d04d86d103a..5cefc57b666 100644 --- a/geonode/layers/api/views.py +++ b/geonode/layers/api/views.py @@ -81,6 +81,7 @@ class DatasetViewSet(ApiPresetsInitializer, MultiLangViewMixin, DynamicModelView DatasetPermissionsFilter, ] queryset = Dataset.objects.all().order_by("-created") + search_fields = ["title", "abstract"] serializer_class = DatasetSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/maps/api/views.py b/geonode/maps/api/views.py index 0927f2bb3a3..9a5c9dcf37a 100644 --- a/geonode/maps/api/views.py +++ b/geonode/maps/api/views.py @@ -64,6 +64,7 @@ class MapViewSet(ApiPresetsInitializer, MultiLangViewMixin, DynamicModelViewSet) MapPermissionsFilter, ] queryset = Map.objects.all().order_by("-created") + search_fields = ["title", "abstract"] serializer_class = MapSerializer pagination_class = GeoNodeApiPagination diff --git a/geonode/people/api/views.py b/geonode/people/api/views.py index 2cf48e35cc8..886d8be5a12 100644 --- a/geonode/people/api/views.py +++ b/geonode/people/api/views.py @@ -55,6 +55,7 @@ class UserViewSet(DynamicModelViewSet): IsOwnerOrAdmin, ] filter_backends = [DynamicFilterBackend, DynamicSortingFilter, DynamicSearchFilter] + search_fields = ["username", "first_name", "last_name"] serializer_class = UserSerializer pagination_class = GeoNodeApiPagination