gravityzone: add full GravityZone integration module
Adds JSON-RPC client, Pydantic schemas, and FastAPI router for Bitdefender GravityZone. Endpoints: status, companies, endpoints, quarantine, and security sweep across all 55 managed client companies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -193,6 +193,8 @@ def _build_sweep_result(summaries) -> GZSweepResult:
|
|||||||
last_seen_dt = datetime.fromisoformat(
|
last_seen_dt = datetime.fromisoformat(
|
||||||
s.last_seen.replace("Z", "+00:00")
|
s.last_seen.replace("Z", "+00:00")
|
||||||
)
|
)
|
||||||
|
if last_seen_dt.tzinfo is None:
|
||||||
|
last_seen_dt = last_seen_dt.replace(tzinfo=timezone.utc)
|
||||||
if last_seen_dt < stale_cutoff:
|
if last_seen_dt < stale_cutoff:
|
||||||
not_seen_recently += 1
|
not_seen_recently += 1
|
||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
@@ -247,5 +249,5 @@ async def sweep_all_clients(
|
|||||||
current_user: dict = Depends(get_current_user),
|
current_user: dict = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
service = get_gravityzone_service()
|
service = get_gravityzone_service()
|
||||||
summaries = await service.security_sweep(ACG_COMPANIES_CONTAINER_ID)
|
summaries = await service.security_sweep_all_clients()
|
||||||
return _build_sweep_result(summaries)
|
return _build_sweep_result(summaries)
|
||||||
|
|||||||
@@ -219,6 +219,39 @@ class GravityZoneService:
|
|||||||
summaries.sort(key=_sort_key)
|
summaries.sort(key=_sort_key)
|
||||||
return summaries
|
return summaries
|
||||||
|
|
||||||
|
async def security_sweep_all_clients(self) -> list[GZEndpointSummary]:
|
||||||
|
companies_result = await self.list_client_companies(per_page=100)
|
||||||
|
if not companies_result.success:
|
||||||
|
logger.warning(
|
||||||
|
f"GravityZone security_sweep_all_clients: list_client_companies failed: "
|
||||||
|
f"{companies_result.error}"
|
||||||
|
)
|
||||||
|
return []
|
||||||
|
|
||||||
|
companies = (companies_result.data or {}).get("items", [])
|
||||||
|
all_summaries: list[GZEndpointSummary] = []
|
||||||
|
|
||||||
|
for company in companies:
|
||||||
|
company_id = company.get("id", "")
|
||||||
|
if not company_id:
|
||||||
|
continue
|
||||||
|
company_summaries = await self.security_sweep(company_id)
|
||||||
|
for s in company_summaries:
|
||||||
|
if not s.company_id:
|
||||||
|
s.company_id = company_id
|
||||||
|
all_summaries.extend(company_summaries)
|
||||||
|
|
||||||
|
def _sort_key(s: GZEndpointSummary) -> tuple:
|
||||||
|
return (
|
||||||
|
not s.infected,
|
||||||
|
not s.signature_outdated,
|
||||||
|
not s.product_outdated,
|
||||||
|
s.name.lower(),
|
||||||
|
)
|
||||||
|
|
||||||
|
all_summaries.sort(key=_sort_key)
|
||||||
|
return all_summaries
|
||||||
|
|
||||||
|
|
||||||
_gravityzone_service: Optional[GravityZoneService] = None
|
_gravityzone_service: Optional[GravityZoneService] = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user