diff --git a/api/src/backend/api/filters.py b/api/src/backend/api/filters.py index c787d714b1..10edc304fd 100644 --- a/api/src/backend/api/filters.py +++ b/api/src/backend/api/filters.py @@ -1,4 +1,5 @@ from datetime import date, datetime, timedelta, timezone +from functools import lru_cache from dateutil.parser import parse from django.conf import settings @@ -19,7 +20,6 @@ from api.constants import SEVERITY_ORDER from api.db_utils import ( FindingDeltaEnumField, InvitationStateEnumField, - ProviderEnumField, SeverityEnumField, StatusEnumField, ) @@ -68,6 +68,18 @@ from api.uuid_utils import ( uuid7_start, ) from api.v1.serializers import TaskBase +from prowler.providers.common.provider import Provider as SDKProvider + + +@lru_cache(maxsize=1) +def get_provider_type_choices(): + """Provider-type filter choices driven by the SDK's available providers + instead of a static enum, so filtering covers external providers too. + + Cached because the installed providers are fixed for the process lifetime + and provider-type filters live on hot list endpoints. + """ + return [(name, name) for name in SDKProvider.get_available_providers()] class CustomDjangoFilterBackend(DjangoFilterBackend): @@ -109,11 +121,11 @@ class BaseProviderFilter(FilterSet): provider_id = UUIDFilter(field_name="provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="provider__id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( field_name="provider__provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) @@ -133,11 +145,11 @@ class BaseScanProviderFilter(FilterSet): provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="scan__provider__id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="scan__provider__provider", choices=Provider.ProviderChoices.choices + field_name="scan__provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( field_name="scan__provider__provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) @@ -155,10 +167,10 @@ class CommonFindingFilters(FilterSet): provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="scan__provider__id", lookup_expr="in") provider_type = ChoiceFilter( - choices=Provider.ProviderChoices.choices, field_name="scan__provider__provider" + choices=get_provider_type_choices(), field_name="scan__provider__provider" ) provider_type__in = ChoiceInFilter( - choices=Provider.ProviderChoices.choices, field_name="scan__provider__provider" + choices=get_provider_type_choices(), field_name="scan__provider__provider" ) provider_uid = CharFilter(field_name="scan__provider__uid", lookup_expr="exact") provider_uid__in = CharInFilter(field_name="scan__provider__uid", lookup_expr="in") @@ -356,18 +368,18 @@ class ProviderFilter(FilterSet): included. Providers with no connection attempt (status is null) are excluded from this filter.""" ) - provider = ChoiceFilter(choices=Provider.ProviderChoices.choices) + provider = ChoiceFilter(choices=get_provider_type_choices()) provider__in = ChoiceInFilter( field_name="provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) provider_type = ChoiceFilter( - choices=Provider.ProviderChoices.choices, field_name="provider" + choices=get_provider_type_choices(), field_name="provider" ) provider_type__in = ChoiceInFilter( field_name="provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) @@ -381,19 +393,14 @@ class ProviderFilter(FilterSet): "inserted_at": ["gte", "lte"], "updated_at": ["gte", "lte"], } - filter_overrides = { - ProviderEnumField: { - "filter_class": CharFilter, - }, - } class ProviderRelationshipFilterSet(FilterSet): provider_type = ChoiceFilter( - choices=Provider.ProviderChoices.choices, field_name="provider__provider" + choices=get_provider_type_choices(), field_name="provider__provider" ) provider_type__in = ChoiceInFilter( - choices=Provider.ProviderChoices.choices, field_name="provider__provider" + choices=get_provider_type_choices(), field_name="provider__provider" ) provider_uid = CharFilter(field_name="provider__uid", lookup_expr="exact") provider_uid__in = CharInFilter(field_name="provider__uid", lookup_expr="in") @@ -998,7 +1005,7 @@ class FindingGroupSummaryFilter(_CheckTitleToCheckIdMixin, FilterSet): provider_id = UUIDFilter(field_name="provider_id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="provider_id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) provider_type__in = CharInFilter(field_name="provider__provider", lookup_expr="in") @@ -1098,7 +1105,7 @@ class LatestFindingGroupSummaryFilter(_CheckTitleToCheckIdMixin, FilterSet): provider_id = UUIDFilter(field_name="provider_id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="provider_id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) provider_type__in = CharInFilter(field_name="provider__provider", lookup_expr="in") @@ -1301,10 +1308,10 @@ class ScanSummaryFilter(FilterSet): provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="scan__provider__id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="scan__provider__provider", choices=Provider.ProviderChoices.choices + field_name="scan__provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( - field_name="scan__provider__provider", choices=Provider.ProviderChoices.choices + field_name="scan__provider__provider", choices=get_provider_type_choices() ) region = CharFilter(field_name="region") @@ -1324,10 +1331,10 @@ class DailySeveritySummaryFilter(FilterSet): provider_id = UUIDFilter(field_name="provider_id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="provider_id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) date_from = DateFilter(method="filter_noop") date_to = DateFilter(method="filter_noop") @@ -1578,11 +1585,11 @@ class ThreatScoreSnapshotFilter(FilterSet): provider_id = UUIDFilter(field_name="provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="provider__id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="provider__provider", choices=Provider.ProviderChoices.choices + field_name="provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( field_name="provider__provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) compliance_id = CharFilter(field_name="compliance_id", lookup_expr="exact") @@ -1621,11 +1628,11 @@ class ResourceGroupOverviewFilter(FilterSet): provider_id = UUIDFilter(field_name="scan__provider__id", lookup_expr="exact") provider_id__in = UUIDInFilter(field_name="scan__provider__id", lookup_expr="in") provider_type = ChoiceFilter( - field_name="scan__provider__provider", choices=Provider.ProviderChoices.choices + field_name="scan__provider__provider", choices=get_provider_type_choices() ) provider_type__in = ChoiceInFilter( field_name="scan__provider__provider", - choices=Provider.ProviderChoices.choices, + choices=get_provider_type_choices(), lookup_expr="in", ) resource_group = CharFilter(field_name="resource_group", lookup_expr="exact") diff --git a/api/src/backend/api/tests/test_filters.py b/api/src/backend/api/tests/test_filters.py new file mode 100644 index 0000000000..7738d247bb --- /dev/null +++ b/api/src/backend/api/tests/test_filters.py @@ -0,0 +1,23 @@ +from api.filters import get_provider_type_choices +from prowler.providers.common.provider import Provider as SDKProvider + + +class TestProviderTypeChoices: + """Provider-type filter choices are driven by the SDK's available providers + so filtering covers external providers, not just a static enum.""" + + def test_choices_track_sdk_available_providers(self): + available = set(SDKProvider.get_available_providers()) + choices = get_provider_type_choices() + + assert {value for value, _ in choices} == available + + def test_choices_include_provider_absent_from_legacy_enum(self): + from api.models import Provider + + legacy = {value for value, _ in Provider.ProviderChoices.choices} + choice_values = {value for value, _ in get_provider_type_choices()} + + # `llm` is exposed by the SDK but is not part of the legacy static enum. + assert "llm" in choice_values + assert "llm" not in legacy