mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat(compliance): Add endpoint to retrieve compliance overviews metadata (#7333)
This commit is contained in:
committed by
Pepe Fagoaga
parent
bd839e1398
commit
a4f5566589
@@ -9,6 +9,7 @@ All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
- Support for developing new integrations [(#7167)](https://github.com/prowler-cloud/prowler/pull/7167).
|
||||
- HTTP Security Headers [(#7289)](https://github.com/prowler-cloud/prowler/pull/7289).
|
||||
- New endpoint to get the compliance overviews metadata [(#7333)](https://github.com/prowler-cloud/prowler/pull/7333).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -233,6 +233,42 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComplianceOverviewFullResponse'
|
||||
description: ''
|
||||
/api/v1/compliance-overviews/metadata:
|
||||
get:
|
||||
operationId: compliance_overviews_metadata_retrieve
|
||||
description: Fetch unique metadata values from a set of compliance overviews.
|
||||
This is useful for dynamic filtering.
|
||||
summary: Retrieve metadata values from compliance overviews
|
||||
parameters:
|
||||
- in: query
|
||||
name: fields[compliance-overviews-metadata]
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- regions
|
||||
description: endpoint return only specific fields in the response on a per-type
|
||||
basis by including a fields[TYPE] query parameter.
|
||||
explode: false
|
||||
- in: query
|
||||
name: filter[scan_id]
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Related scan ID.
|
||||
required: true
|
||||
tags:
|
||||
- Compliance Overview
|
||||
security:
|
||||
- jwtAuth: []
|
||||
responses:
|
||||
'200':
|
||||
content:
|
||||
application/vnd.api+json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ComplianceOverviewMetadataResponse'
|
||||
description: ''
|
||||
/api/v1/findings:
|
||||
get:
|
||||
operationId: findings_list
|
||||
@@ -3674,6 +3710,7 @@ paths:
|
||||
- name
|
||||
- manage_users
|
||||
- manage_account
|
||||
- manage_integrations
|
||||
- manage_providers
|
||||
- manage_scans
|
||||
- permission_state
|
||||
@@ -3792,6 +3829,8 @@ paths:
|
||||
- -manage_users
|
||||
- manage_account
|
||||
- -manage_account
|
||||
- manage_integrations
|
||||
- -manage_integrations
|
||||
- manage_providers
|
||||
- -manage_providers
|
||||
- manage_scans
|
||||
@@ -3867,6 +3906,7 @@ paths:
|
||||
- name
|
||||
- manage_users
|
||||
- manage_account
|
||||
- manage_integrations
|
||||
- manage_providers
|
||||
- manage_scans
|
||||
- permission_state
|
||||
@@ -4236,6 +4276,17 @@ paths:
|
||||
description: |-
|
||||
* `scheduled` - Scheduled
|
||||
* `manual` - Manual
|
||||
- in: query
|
||||
name: include
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- provider
|
||||
description: include query parameter to allow the client to customize which
|
||||
related resources should be returned.
|
||||
explode: false
|
||||
- name: page[number]
|
||||
required: false
|
||||
in: query
|
||||
@@ -4350,6 +4401,17 @@ paths:
|
||||
format: uuid
|
||||
description: A UUID string identifying this scan.
|
||||
required: true
|
||||
- in: query
|
||||
name: include
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- provider
|
||||
description: include query parameter to allow the client to customize which
|
||||
related resources should be returned.
|
||||
explode: false
|
||||
tags:
|
||||
- Scan
|
||||
security:
|
||||
@@ -6105,6 +6167,40 @@ components:
|
||||
$ref: '#/components/schemas/ComplianceOverviewFull'
|
||||
required:
|
||||
- data
|
||||
ComplianceOverviewMetadata:
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
- id
|
||||
additionalProperties: false
|
||||
properties:
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ComplianceOverviewMetadataTypeEnum'
|
||||
description: The [type](https://jsonapi.org/format/#document-resource-object-identification)
|
||||
member is used to describe resource objects that share common attributes
|
||||
and relationships.
|
||||
id: {}
|
||||
attributes:
|
||||
type: object
|
||||
properties:
|
||||
regions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- regions
|
||||
ComplianceOverviewMetadataResponse:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/ComplianceOverviewMetadata'
|
||||
required:
|
||||
- data
|
||||
ComplianceOverviewMetadataTypeEnum:
|
||||
type: string
|
||||
enum:
|
||||
- compliance-overviews-metadata
|
||||
Finding:
|
||||
type: object
|
||||
required:
|
||||
@@ -8398,6 +8494,8 @@ components:
|
||||
type: boolean
|
||||
manage_account:
|
||||
type: boolean
|
||||
manage_integrations:
|
||||
type: boolean
|
||||
manage_providers:
|
||||
type: boolean
|
||||
manage_scans:
|
||||
@@ -10050,6 +10148,8 @@ components:
|
||||
type: boolean
|
||||
manage_account:
|
||||
type: boolean
|
||||
manage_integrations:
|
||||
type: boolean
|
||||
manage_providers:
|
||||
type: boolean
|
||||
manage_scans:
|
||||
@@ -10179,6 +10279,8 @@ components:
|
||||
type: boolean
|
||||
manage_account:
|
||||
type: boolean
|
||||
manage_integrations:
|
||||
type: boolean
|
||||
manage_providers:
|
||||
type: boolean
|
||||
manage_scans:
|
||||
@@ -10313,6 +10415,8 @@ components:
|
||||
type: boolean
|
||||
manage_account:
|
||||
type: boolean
|
||||
manage_integrations:
|
||||
type: boolean
|
||||
manage_providers:
|
||||
type: boolean
|
||||
manage_scans:
|
||||
|
||||
@@ -14,6 +14,7 @@ from django.urls import reverse
|
||||
from rest_framework import status
|
||||
|
||||
from api.models import (
|
||||
ComplianceOverview,
|
||||
Integration,
|
||||
Invitation,
|
||||
Membership,
|
||||
@@ -4508,6 +4509,33 @@ class TestComplianceOverviewViewSet:
|
||||
assert len(response.json()["data"]) == 1
|
||||
assert response.json()["data"][0]["id"] == str(compliance_overview1.id)
|
||||
|
||||
def test_compliance_overview_metadata(
|
||||
self, authenticated_client, compliance_overviews_fixture
|
||||
):
|
||||
response = authenticated_client.get(
|
||||
reverse("complianceoverview-metadata"),
|
||||
{"filter[scan_id]": str(compliance_overviews_fixture[0].scan_id)},
|
||||
)
|
||||
data = response.json()
|
||||
|
||||
expected_regions = set(
|
||||
ComplianceOverview.objects.all()
|
||||
.values_list("region", flat=True)
|
||||
.distinct("region")
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert data["data"]["type"] == "compliance-overviews-metadata"
|
||||
assert data["data"]["id"] is None
|
||||
assert set(data["data"]["attributes"]["regions"]) == expected_regions
|
||||
|
||||
def test_compliance_overview_metadata_missing_scan_id(self, authenticated_client):
|
||||
# Attempt to list compliance overviews without providing filter[scan_id]
|
||||
response = authenticated_client.get(reverse("complianceoverview-metadata"))
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert response.json()["errors"][0]["source"]["pointer"] == "filter[scan_id]"
|
||||
assert response.json()["errors"][0]["code"] == "required"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestOverviewViewSet:
|
||||
|
||||
@@ -851,6 +851,10 @@ class ScanSerializer(RLSSerializer):
|
||||
"url",
|
||||
]
|
||||
|
||||
included_serializers = {
|
||||
"provider": "api.v1.serializers.ProviderIncludeSerializer",
|
||||
}
|
||||
|
||||
|
||||
class ScanIncludeSerializer(RLSSerializer):
|
||||
trigger = serializers.ChoiceField(
|
||||
@@ -1897,6 +1901,13 @@ class ComplianceOverviewFullSerializer(ComplianceOverviewSerializer):
|
||||
return obj.requirements
|
||||
|
||||
|
||||
class ComplianceOverviewMetadataSerializer(serializers.Serializer):
|
||||
regions = serializers.ListField(child=serializers.CharField(), allow_empty=True)
|
||||
|
||||
class Meta:
|
||||
resource_name = "compliance-overviews-metadata"
|
||||
|
||||
|
||||
# Overviews
|
||||
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ from api.rls import Tenant
|
||||
from api.utils import CustomOAuth2Client, validate_invitation
|
||||
from api.v1.serializers import (
|
||||
ComplianceOverviewFullSerializer,
|
||||
ComplianceOverviewMetadataSerializer,
|
||||
ComplianceOverviewSerializer,
|
||||
FindingDynamicFilterSerializer,
|
||||
FindingMetadataSerializer,
|
||||
@@ -2057,6 +2058,21 @@ class RoleProviderGroupRelationshipView(RelationshipView, BaseRLSViewSet):
|
||||
description="Fetch detailed information about a specific compliance overview by its ID, including detailed "
|
||||
"requirement information and check's status.",
|
||||
),
|
||||
metadata=extend_schema(
|
||||
tags=["Compliance Overview"],
|
||||
summary="Retrieve metadata values from compliance overviews",
|
||||
description="Fetch unique metadata values from a set of compliance overviews. This is useful for dynamic "
|
||||
"filtering.",
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="filter[scan_id]",
|
||||
required=True,
|
||||
type=OpenApiTypes.UUID,
|
||||
location=OpenApiParameter.QUERY,
|
||||
description="Related scan ID.",
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
@method_decorator(CACHE_DECORATOR, name="list")
|
||||
@method_decorator(CACHE_DECORATOR, name="retrieve")
|
||||
@@ -2118,6 +2134,8 @@ class ComplianceOverviewViewSet(BaseRLSViewSet):
|
||||
def get_serializer_class(self):
|
||||
if self.action == "retrieve":
|
||||
return ComplianceOverviewFullSerializer
|
||||
elif self.action == "metadata":
|
||||
return ComplianceOverviewMetadataSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
@@ -2134,6 +2152,35 @@ class ComplianceOverviewViewSet(BaseRLSViewSet):
|
||||
)
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
@action(detail=False, methods=["get"], url_name="metadata")
|
||||
def metadata(self, request):
|
||||
scan_id = request.query_params.get("filter[scan_id]")
|
||||
if not scan_id:
|
||||
raise ValidationError(
|
||||
[
|
||||
{
|
||||
"detail": "This query parameter is required.",
|
||||
"status": 400,
|
||||
"source": {"pointer": "filter[scan_id]"},
|
||||
"code": "required",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
tenant_id = self.request.tenant_id
|
||||
|
||||
regions = list(
|
||||
ComplianceOverview.objects.filter(tenant_id=tenant_id, scan_id=scan_id)
|
||||
.values_list("region", flat=True)
|
||||
.order_by("region")
|
||||
.distinct()
|
||||
)
|
||||
result = {"regions": regions}
|
||||
|
||||
serializer = self.get_serializer(data=result)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
@extend_schema(tags=["Overview"])
|
||||
@extend_schema_view(
|
||||
|
||||
@@ -43,3 +43,44 @@ export const getCompliancesOverview = async ({
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const getComplianceOverviewMetadataInfo = async ({
|
||||
query = "",
|
||||
sort = "",
|
||||
filters = {},
|
||||
}) => {
|
||||
const session = await auth();
|
||||
|
||||
const url = new URL(`${apiBaseUrl}/compliance-overviews/metadata`);
|
||||
|
||||
if (query) url.searchParams.append("filter[search]", query);
|
||||
if (sort) url.searchParams.append("sort", sort);
|
||||
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
// Define filters to exclude
|
||||
if (key !== "filter[search]") {
|
||||
url.searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch compliance overview metadata info: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const parsedData = parseStringify(await response.json());
|
||||
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching compliance overview metadata info:", error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Spacer } from "@nextui-org/react";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getCompliancesOverview } from "@/actions/compliances";
|
||||
import { getMetadataInfo } from "@/actions/findings";
|
||||
import { getComplianceOverviewMetadataInfo } from "@/actions/compliances";
|
||||
import { getProvider } from "@/actions/providers";
|
||||
import { getScans } from "@/actions/scans";
|
||||
import {
|
||||
@@ -70,10 +70,10 @@ export default async function Compliance({
|
||||
searchParams.scanId || expandedScansData[0]?.id || null;
|
||||
const query = (filters["filter[search]"] as string) || "";
|
||||
|
||||
const metadataInfoData = await getMetadataInfo({
|
||||
const metadataInfoData = await getComplianceOverviewMetadataInfo({
|
||||
query,
|
||||
filters: {
|
||||
"filter[scan]": selectedScanId,
|
||||
"filter[scan_id]": selectedScanId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user