Skip to content

Commit

Permalink
wip re-write
Browse files Browse the repository at this point in the history
  • Loading branch information
jdobes committed Oct 16, 2023
1 parent 0a38605 commit 0f445c2
Showing 1 changed file with 107 additions and 71 deletions.
178 changes: 107 additions & 71 deletions manager/status_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
from common.peewee_model import InsightsRule
from common.peewee_model import RHAccount
from common.peewee_model import Status
from common.peewee_model import SystemCveData
from common.peewee_model import SystemPlatform
from common.peewee_model import SystemVulnerabilities
from common.peewee_model import SystemVulnerablePackage
from common.peewee_model import VulnerablePackageCVE

LOGGER = get_logger(__name__)

Expand All @@ -50,95 +53,131 @@ class PatchStatus(PatchRequest):
_endpoint_name = r"/v1/status"

@staticmethod
def _prepare_data(data, rh_account_id):

def _prepare_data(data):
if "inventory_id" in data:
in_inventory_id_list = parse_str_or_list(data["inventory_id"])
else:
# change status for all systems for given CVE(s)
in_inventory_id_list = None

in_cve_list = parse_str_or_list(data["cve"])
in_status_id = data.get("status_id")
in_status_text = data.get("status_text", "").strip() if data.get("status_text", "").strip() else None

status_to_cves_map = {}
status_text_to_cves_map = {}
if "status_id" in data:
# single status for all CVEs
status_to_cves_map[data["status_id"]] = in_cve_list
if "status_text" in data:
# single status for all CVEs
try:
key = data["status_text"].strip() if data["status_text"].strip() else None
except AttributeError:
key = None
status_text_to_cves_map[key] = in_cve_list
# if neither of status_id or status_text is set => inherit from CVE-level
if not status_to_cves_map and not status_text_to_cves_map:
# use CVE-level status if status not specified
cve_details = (CveAccountData.select(CveMetadata.cve, CveAccountData.status_id, CveAccountData.status_text)
.join(CveMetadata, on=(CveAccountData.cve_id == CveMetadata.id))
.where((CveAccountData.rh_account_id == rh_account_id) &
(CveMetadata.cve << in_cve_list))
.dicts())
return in_inventory_id_list, in_cve_list, in_status_id, in_status_text

found_cves = set()
for cve in cve_details:
status_to_cves_map.setdefault(cve["status_id"], []).append(cve["cve"])
status_text_to_cves_map.setdefault(cve["status_text"], []).append(cve["cve"])
found_cves.add(cve["cve"])
# not found CVEs have 0 status by default, status_text is null
for cve in in_cve_list:
if cve not in found_cves:
status_to_cves_map.setdefault(0, []).append(cve)
status_text_to_cves_map.setdefault(None, []).append(cve)
@staticmethod
def _apply_system_list_filter(query, rh_account_id, in_inventory_id_list):
query = cyndi_join(query)
query = query.where((SystemPlatform.rh_account_id == rh_account_id) &
(SystemPlatform.opt_out == False) &
(SystemPlatform.stale == False) &
(SystemPlatform.when_deleted.is_null(True)) &
(SystemPlatform.host_type.is_null(True)))
if in_inventory_id_list is not None:
query = query.where(SystemPlatform.inventory_id << in_inventory_id_list)
return query

return in_inventory_id_list, status_to_cves_map, status_text_to_cves_map
@classmethod
def _get_current_status(cls, rh_account_id, in_inventory_id_list, in_cve_list):
# pair status
system_cve_details = (SystemCveData.select(SystemPlatform.inventory_id, CveMetadata.cve,
SystemCveData.status_id, SystemCveData.status_text)
.join(CveMetadata, on=(SystemCveData.cve_id == CveMetadata.id))
.join(SystemPlatform, on=(SystemCveData.system_id == SystemPlatform.id))
.where(CveMetadata.cve << in_cve_list)
.dicts())
system_cve_details = cls._apply_system_list_filter(system_cve_details, rh_account_id, in_inventory_id_list)
current_status = {}
for system_cve_detail in system_cve_details:
current_status.setdefault(system_cve_detail["cve"], {})[system_cve_detail["inventory_id"]] = \
(system_cve_detail["status_id"], system_cve_detail["status_text"])

# global status
cve_details = (CveAccountData.select(CveMetadata.cve, CveAccountData.status_id, CveAccountData.status_text)
.join(CveMetadata, on=(CveAccountData.cve_id == CveMetadata.id))
.where((CveAccountData.rh_account_id == rh_account_id) &
(CveMetadata.cve << in_cve_list))
.dicts())
for cve_detail in cve_details:
current_status.setdefault(cve_detail["cve"], {})["global"] = (cve_detail["status_id"], cve_detail["status_text"])
return current_status

@staticmethod
def _build_update_condition(rh_account_id, systems, status_cve_list):
# pylint: disable=singleton-comparison
return ((SystemVulnerabilities.rh_account_id == rh_account_id) &
(SystemVulnerabilities.system_id << systems) &
(SystemVulnerabilities.cve_id <<
(CveMetadata.select(CveMetadata.id).where(
CveMetadata.cve << status_cve_list))) &
((SystemVulnerabilities.when_mitigated.is_null(True)) |
((SystemVulnerabilities.mitigation_reason.is_null(True)) &
(SystemVulnerabilities.rule_id << (InsightsRule.select(InsightsRule.id)
.where((InsightsRule.active == True) & (InsightsRule.rule_only == False)))))))
@classmethod
def _get_affected_pairs(cls, rh_account_id, in_inventory_id_list, in_cve_list):
affected_pairs = set()
fixable_pairs = (SystemVulnerabilities.select(SystemPlatform.inventory_id, CveMetadata.cve)
.join(CveMetadata, on=(SystemVulnerabilities.cve_id == CveMetadata.id))
.join(SystemPlatform, on=(SystemVulnerabilities.system_id == SystemPlatform.id))
.where(SystemVulnerabilities.rh_account_id == rh_account_id)
.where((SystemVulnerabilities.cve_id <<
(CveMetadata.select(CveMetadata.id).where(
CveMetadata.cve << in_cve_list))) &
((SystemVulnerabilities.when_mitigated.is_null(True)) |
((SystemVulnerabilities.mitigation_reason.is_null(True)) &
(SystemVulnerabilities.rule_id << (InsightsRule.select(InsightsRule.id)
.where((InsightsRule.active == True) & (InsightsRule.rule_only == False)))))))
.dicts())
fixable_pairs = cls._apply_system_list_filter(fixable_pairs, rh_account_id, in_inventory_id_list)
for pair in fixable_pairs:
affected_pairs.add((pair["inventory_id"], pair["cve"]))

unfixable_pairs = (SystemVulnerablePackage.select(SystemPlatform.inventory_id, CveMetadata.cve)
.join(VulnerablePackageCVE, on=(SystemVulnerablePackage.vulnerable_package_id == VulnerablePackageCVE.vulnerable_package_id))
.join(SystemPlatform, on=(SystemVulnerablePackage.system_id == SystemPlatform.id))
.join(CveMetadata, on=(VulnerablePackageCVE.cve_id == CveMetadata.id))
.where(SystemVulnerablePackage.rh_account_id == rh_account_id)
.where((VulnerablePackageCVE.cve_id <<
(CveMetadata.select(CveMetadata.id).where(
CveMetadata.cve << in_cve_list))))
.dicts())
unfixable_pairs = cls._apply_system_list_filter(unfixable_pairs, rh_account_id, in_inventory_id_list)
for pair in unfixable_pairs:
affected_pairs.add((pair["inventory_id"], pair["cve"]))
return affected_pairs

@classmethod
@RBAC.need_permissions(RbacRoutePermissions.SYSTEM_CVE_STATUS_EDIT)
def handle_patch(cls, **kwargs):
"""Update the "status" field for a system/cve combination"""
# pylint: disable=singleton-comparison
data = kwargs["data"]

try:
rh_account_id = get_or_create_account()
in_inventory_id_list, status_to_cves_map, status_text_to_cves_map = cls._prepare_data(data, rh_account_id)
systems = (SystemPlatform.select(SystemPlatform.id)
.where((SystemPlatform.rh_account_id == rh_account_id) &
(SystemPlatform.opt_out == False) &
(SystemPlatform.stale == False) &
(SystemPlatform.when_deleted.is_null(True)) &
(SystemPlatform.host_type.is_null(True))))
in_inventory_id_list, in_cve_list, in_status_id, in_status_text = cls._prepare_data(data)
if in_inventory_id_list is not None:
systems = systems.where(SystemPlatform.inventory_id << in_inventory_id_list)
rows_modified = set()
# set statuses and their CVE lists
for status_id, status_cve_list in status_to_cves_map.items():
status_id_update = (SystemVulnerabilities.update(status_id=status_id)
.where(cls._build_update_condition(rh_account_id, systems, status_cve_list))
.returning(SystemVulnerabilities.id))
rows_modified.update([row.id for row in status_id_update])

for status_text, status_cve_list in status_text_to_cves_map.items():
status_text_update = (SystemVulnerabilities.update(status_text=status_text)
.where(cls._build_update_condition(rh_account_id, systems, status_cve_list))
.returning(SystemVulnerabilities.id))
rows_modified.update([row.id for row in status_text_update])
LOGGER.info("in_inventory_id_list: %s", len(in_inventory_id_list))
LOGGER.info("in_inventory_id_list: %s", in_inventory_id_list)
LOGGER.info("in_cve_list: %s", len(in_cve_list))
LOGGER.info("in_cve_list: %s", in_cve_list)

# current status for system-CVE pairs and CVEs
current_status = cls._get_current_status(rh_account_id, in_inventory_id_list, in_cve_list)
LOGGER.info("current_status: %s", len(current_status))
LOGGER.info("current_status: %s", current_status)

# get system-CVE pairs for which status should be changed (may result in inserting, updating or deleting rows in status table)
affected_pairs = cls._get_affected_pairs(rh_account_id, in_inventory_id_list, in_cve_list)
LOGGER.info("affected_pairs: %s", len(affected_pairs))
LOGGER.info("affected_pairs: %s", affected_pairs)

if not affected_pairs:
# sysid/cve/acct combination does not exist
return cls.format_exception("inventory_id/cve must exist and inventory_id must be visible to user", 404)

to_insert = []
to_update = []
to_delete = []
for inventory_id, cve in affected_pairs:
# set global CVE status_id if there is no status_id in request
if in_status_id is None and in_status_text is None:
target_status_id, target_status_text = current_status.get(cve, {}).get("global", (0, None))
else:
target_status_id = in_status_id
target_status_text = in_status_text
current_status_id, current_status_text = current_status.get(cve, {}).get(inventory_id, (0, None))

rows_modified = set()
if rows_modified:
RHAccount.update(last_status_change=datetime.now(timezone.utc)).where(RHAccount.id == rh_account_id).execute()
updated_details = (SystemVulnerabilities.select(SystemPlatform.inventory_id, CveMetadata.cve)
Expand All @@ -150,14 +189,11 @@ def handle_patch(cls, **kwargs):
updated = []
for updated_row in updated_details:
updated.append({"inventory_id": updated_row["inventory_id"], "cve": updated_row["cve"]})
if not updated:
# sysid/cve/acct combination does not exist
return cls.format_exception("inventory_id/cve must exist and inventory_id must be visible to user", 404)
except (IntegrityError, psycopg2IntegrityError, DataError) as value_error:
# usually means bad-status-id
LOGGER.error(str(value_error))
DB.rollback()
return cls.format_exception(f"status_id={list(status_to_cves_map.keys())} is invalid", 400)
return cls.format_exception(f"status_id= is invalid", 400) #fixme
except ValueError as value_error:
LOGGER.exception("Error during setting status (ValueError):")
DB.rollback()
Expand Down

0 comments on commit 0f445c2

Please sign in to comment.