Skip to content

Commit

Permalink
fix(discovery): handle missing permissions case for public-search-fields
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlougheed committed Sep 5, 2024
1 parent 59f5dec commit 22a7154
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 8 deletions.
18 changes: 15 additions & 3 deletions chord_metadata_service/discovery/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from bento_lib.responses import errors
from django.conf import settings
from drf_spectacular.utils import extend_schema, inline_serializer
from functools import partial
from operator import is_not
from rest_framework import serializers, status
from rest_framework.decorators import permission_classes
from rest_framework.request import Request as DrfRequest
Expand All @@ -25,6 +27,9 @@
from .utils import get_discovery_data_type_permissions, get_discovery_field_set_permissions


is_not_none = partial(is_not, None)


@extend_schema(
description="Public search fields with their configuration",
responses={
Expand Down Expand Up @@ -75,14 +80,21 @@ async def _get_field_response(field) -> dict | None:
"options": await get_field_options(field, discovery, field_permissions[field]),
}

async def _get_section_response(section) -> dict:
async def _get_section_response(section) -> dict | None:
section_fields = list(filter(is_not_none, await asyncio.gather(*map(_get_field_response, section["fields"]))))

if not section_fields:
# No access to any field in the section (they were all None -> they all got filtered out), so we want to
# filter the section itself out - return a None which will get filtered out below.
return None

return {
**section,
"fields": await asyncio.gather(*filter(None, map(_get_field_response, section["fields"]))),
"fields": section_fields,
}

return Response({
"sections": await asyncio.gather(*map(_get_section_response, discovery["search"])),
"sections": list(filter(is_not_none, await asyncio.gather(*map(_get_section_response, discovery["search"])))),
})


Expand Down
18 changes: 13 additions & 5 deletions chord_metadata_service/discovery/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def discovery_test_configs() -> TestDiscoveryConfigsDict:
def test_public_search_fields_configured(self):
search_fields_url = reverse("public-search-fields")

subtest_params: list[tuple[DTAccessLevel, str, int, TestDiscoveryConfigKey]] = [
subtest_params: list[tuple[DTAccessLevel, str, int, TestDiscoveryConfigKey | dict]] = [
# SCOPE: whole node
("counts", "", status.HTTP_200_OK, "public"),
# SCOPE: project_a (same discovery as whole node)
Expand All @@ -155,19 +155,27 @@ def test_public_search_fields_configured(self):
("counts", "?project=not-a-uuid", status.HTTP_404_NOT_FOUND, "none"),
# invalid UUID for dataset
("counts", "?dataset=not-a-uuid", status.HTTP_404_NOT_FOUND, "none"),
# ------------------------------------------ lacking permissions ------------------------------------------
# - no sections with permissions -> a response with no sections available
("none", "", status.HTTP_200_OK, {"sections": []}),
("bool", f"?project={str(self.id_proj_a)}", status.HTTP_200_OK, {"sections": []}),
]

# use key aliases for configs to make subtest failure output more readable
dtc = self.discovery_test_configs() # to get injected CONFIG_PUBLIC, need to calculate this in-test

for params in subtest_params:
with self.subTest(params=params):
level, qp, expected_status_code, config_key = params
expected_body_config: DiscoveryConfig | None = dtc[config_key]
level, qp, expected_status_code, config_key_or_res = params
res = self.dt_get(level, f"{search_fields_url}{qp}")
self.assertEqual(res.status_code, expected_status_code)
if expected_body_config is not None:
self.assert_response_section_fields(res.json(), expected_body_config)

if isinstance(config_key_or_res, dict):
self.assertDictEqual(res.json(), config_key_or_res)
else:
expected_body_config: DiscoveryConfig | None = dtc[config_key_or_res]
if expected_body_config is not None:
self.assert_response_section_fields(res.json(), expected_body_config)

@override_settings(CONFIG_PUBLIC={})
def test_public_search_fields_not_configured(self):
Expand Down

0 comments on commit 22a7154

Please sign in to comment.