feat(bitdefender): expand GravityZone control surface + correct policy docs
Re-verified the live tenant's full API scope and wrapped the modules the key allows but the skill didn't expose. New CLI subcommands: - assign-policy (gated) — apply an existing policy to endpoints/groups (param shape policyId+targetIds verified live) - reports, accounts, notif-settings, scan-tasks — read - push-settings / push-stats / push-set (gated) — push event service (status param verified; needs a receiver URL to enable) Corrections from live probing: - policies are NOT shallow: getPolicyDetails returns the FULL granular config. Removed the false "shallow" warning; documented read+assign, console-only authoring. - raw now gates assignPolicy + setPushEventSettings. - documented dead modules (patchmanagement/phasr/maintenancewindows/integrations, incidents.getIncidentsList) and unconfigured-push handled cleanly (rc0, no errorlog). selftest 29/29 -> 42/42, all green against the live tenant. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -620,6 +620,127 @@ class GravityZoneClient:
|
||||
"incidents", "removeFromBlocklist", {"hashItemId": hash_item_id}
|
||||
)
|
||||
|
||||
# ======================================================================
|
||||
# POLICY ASSIGNMENT (state-changing; gate behind --confirm at call site)
|
||||
# ----------------------------------------------------------------------
|
||||
# NOTE: getPolicyDetails (above) returns the FULL granular module config
|
||||
# (verified live 2026-06-21 — the earlier "shallow only" claim was wrong).
|
||||
# The Public API still has NO create/edit/clone policy method — authoring
|
||||
# stays in the console — but assigning an EXISTING policy is supported here.
|
||||
# ======================================================================
|
||||
def assign_policy(
|
||||
self,
|
||||
policy_id: str,
|
||||
target_ids: list[str],
|
||||
force_inheritance: bool = False,
|
||||
) -> Any:
|
||||
"""Assign an existing policy to endpoints/groups (network.assignPolicy).
|
||||
|
||||
Param shape VERIFIED LIVE via validation probe (2026-06-21): requires
|
||||
`policyId` and `targetIds` (a list of endpoint/group ids).
|
||||
`forcePolicyInheritance` is optional. STATE-CHANGING — gate at the call
|
||||
site behind --confirm.
|
||||
"""
|
||||
params: dict = {"policyId": policy_id, "targetIds": target_ids}
|
||||
if force_inheritance:
|
||||
params["forcePolicyInheritance"] = True
|
||||
return self._jsonrpc_request("network", "assignPolicy", params)
|
||||
|
||||
def list_scan_tasks(
|
||||
self,
|
||||
page: int = 1,
|
||||
per_page: int = 100,
|
||||
name: Optional[str] = None,
|
||||
status: Optional[int] = None,
|
||||
) -> dict:
|
||||
"""List scan tasks (network.getScanTasksList). VERIFIED LIVE."""
|
||||
params: dict = {"page": page, "perPage": per_page}
|
||||
if name is not None:
|
||||
params["name"] = name
|
||||
if status is not None:
|
||||
params["status"] = status
|
||||
return self._jsonrpc_request("network", "getScanTasksList", params) or {}
|
||||
|
||||
# ======================================================================
|
||||
# REPORTS (module `/reports`) — VERIFIED LIVE
|
||||
# ======================================================================
|
||||
def list_reports(self, page: int = 1, per_page: int = 100) -> dict:
|
||||
"""List saved reports (reports.getReportsList)."""
|
||||
return self._jsonrpc_request(
|
||||
"reports", "getReportsList", {"page": page, "perPage": per_page}
|
||||
) or {}
|
||||
|
||||
def get_report_links(self, report_id: str) -> Any:
|
||||
"""Get download links for a generated report (reports.getDownloadLinks).
|
||||
|
||||
Param name `reportId` is the candidate — confirm against the console if
|
||||
it errors.
|
||||
"""
|
||||
return self._jsonrpc_request(
|
||||
"reports", "getDownloadLinks", {"reportId": report_id}
|
||||
)
|
||||
|
||||
# ======================================================================
|
||||
# ACCOUNTS (module `/accounts`) — VERIFIED LIVE (read)
|
||||
# ======================================================================
|
||||
def list_accounts(self, page: int = 1, per_page: int = 100) -> dict:
|
||||
"""List GravityZone console accounts/users (accounts.getAccountsList)."""
|
||||
return self._jsonrpc_request(
|
||||
"accounts", "getAccountsList", {"page": page, "perPage": per_page}
|
||||
) or {}
|
||||
|
||||
def get_notifications_settings(self) -> dict:
|
||||
"""Notification configuration (accounts.getNotificationsSettings)."""
|
||||
return self._jsonrpc_request(
|
||||
"accounts", "getNotificationsSettings", {}
|
||||
) or {}
|
||||
|
||||
# ======================================================================
|
||||
# PUSH EVENT SERVICE (module `/push`)
|
||||
# ----------------------------------------------------------------------
|
||||
# `get`/`stats` are read (but error when the service was never configured —
|
||||
# that is an EXPECTED state, not a failure; the CLI handles it cleanly).
|
||||
# `set` is STATE-CHANGING (it configures where GravityZone POSTs security
|
||||
# events) — gate behind --confirm at the call site.
|
||||
# ======================================================================
|
||||
def get_push_settings(self) -> dict:
|
||||
"""Current push event service settings (push.getPushEventSettings)."""
|
||||
return self._jsonrpc_request("push", "getPushEventSettings", {}) or {}
|
||||
|
||||
def get_push_stats(self) -> dict:
|
||||
"""Push event service delivery stats (push.getPushEventStats)."""
|
||||
return self._jsonrpc_request("push", "getPushEventStats", {}) or {}
|
||||
|
||||
def set_push_settings(
|
||||
self,
|
||||
status: int,
|
||||
service_type: str = "jsonRPC",
|
||||
url: Optional[str] = None,
|
||||
require_valid_ssl: bool = True,
|
||||
authorization: Optional[str] = None,
|
||||
subscribe_event_types: Optional[dict] = None,
|
||||
) -> Any:
|
||||
"""Configure the GravityZone Push event service (push.setPushEventSettings).
|
||||
|
||||
`status` (1=on / 0=off) is REQUIRED (verified via validation probe
|
||||
2026-06-21). When enabling, `serviceSettings.url` is the receiver
|
||||
endpoint GravityZone POSTs events to. The nested shape
|
||||
(serviceType/serviceSettings/subscribeToEventTypes) follows Bitdefender's
|
||||
documented push API and is UNVERIFIED beyond `status` on this tenant —
|
||||
confirm the first successful enable against the live response.
|
||||
STATE-CHANGING — gate at the call site behind --confirm.
|
||||
"""
|
||||
params: dict = {"status": status, "serviceType": service_type}
|
||||
service_settings: dict = {"requireValidSslCertificate": require_valid_ssl}
|
||||
if url is not None:
|
||||
service_settings["url"] = url
|
||||
if authorization is not None:
|
||||
service_settings["authorization"] = authorization
|
||||
params["serviceSettings"] = service_settings
|
||||
if subscribe_event_types is not None:
|
||||
params["subscribeToEventTypes"] = subscribe_event_types
|
||||
return self._jsonrpc_request("push", "setPushEventSettings", params)
|
||||
|
||||
# ======================================================================
|
||||
# CACHE LAYER (identity / structure only — never volatile status)
|
||||
# ======================================================================
|
||||
|
||||
Reference in New Issue
Block a user