updates
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
# type: ignore
|
||||
# TODO: Handle typing issues
|
||||
import logging
|
||||
import json as json_parser
|
||||
from collections import defaultdict
|
||||
from typing import Iterable, List, Dict, Any
|
||||
|
||||
from safety.formatter import FormatterAPI
|
||||
from safety.models import SafetyEncoder
|
||||
from safety.output_utils import get_report_brief_info
|
||||
from safety.safety import find_vulnerabilities_fixed
|
||||
from safety.util import get_basic_announcements, SafetyContext
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def build_json_report(
|
||||
announcements: List[Dict],
|
||||
vulnerabilities: List[Dict],
|
||||
remediations: Dict[str, Any],
|
||||
packages: List[Any],
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Build a JSON report for vulnerabilities, remediations, and packages.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
vulnerabilities (List[Dict]): List of vulnerabilities.
|
||||
remediations (Dict[str, Any]): Remediation data.
|
||||
packages (List[Any]): List of packages.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: JSON report.
|
||||
"""
|
||||
vulns_ignored = [vuln.to_dict() for vuln in vulnerabilities if vuln.ignored]
|
||||
vulns = [vuln.to_dict() for vuln in vulnerabilities if not vuln.ignored]
|
||||
|
||||
report = get_report_brief_info(
|
||||
as_dict=True,
|
||||
report_type=1,
|
||||
vulnerabilities_found=len(vulns),
|
||||
vulnerabilities_ignored=len(vulns_ignored),
|
||||
remediations_recommended=remediations,
|
||||
)
|
||||
|
||||
if "using_sentence" in report:
|
||||
del report["using_sentence"]
|
||||
|
||||
remed = {}
|
||||
for k, v in remediations.items():
|
||||
if k not in remed:
|
||||
remed[k] = {"requirements": v}
|
||||
|
||||
remed[k]["current_version"] = None
|
||||
remed[k]["vulnerabilities_found"] = None
|
||||
remed[k]["recommended_version"] = None
|
||||
remed[k]["other_recommended_versions"] = []
|
||||
remed[k]["more_info_url"] = None
|
||||
|
||||
return {
|
||||
"report_meta": report,
|
||||
"scanned_packages": {p.name: p.to_dict(short_version=True) for p in packages},
|
||||
"affected_packages": {v.pkg.name: v.pkg.to_dict() for v in vulnerabilities},
|
||||
"announcements": [
|
||||
{"type": item.get("type"), "message": item.get("message")}
|
||||
for item in get_basic_announcements(announcements)
|
||||
],
|
||||
"vulnerabilities": vulns,
|
||||
"ignored_vulnerabilities": vulns_ignored,
|
||||
"remediations": remed,
|
||||
}
|
||||
|
||||
|
||||
class JsonReport(FormatterAPI):
|
||||
"""Json report, for when the output is input for something else"""
|
||||
|
||||
VERSIONS = ("0.5", "1.1")
|
||||
|
||||
def __init__(self, version="1.1", **kwargs):
|
||||
"""
|
||||
Initialize JsonReport with the specified version.
|
||||
|
||||
Args:
|
||||
version (str): Report version.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.version: str = version if version in self.VERSIONS else "1.1"
|
||||
|
||||
def render_vulnerabilities(
|
||||
self,
|
||||
announcements: List[Dict],
|
||||
vulnerabilities: List[Dict],
|
||||
remediations: Dict[str, Any],
|
||||
full: bool,
|
||||
packages: List[Any],
|
||||
fixes: Iterable = (),
|
||||
) -> str:
|
||||
"""
|
||||
Render vulnerabilities in JSON format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
vulnerabilities (List[Dict]): List of vulnerabilities.
|
||||
remediations (Dict[str, Any]): Remediation data.
|
||||
full (bool): Flag indicating full output.
|
||||
packages (List[Any]): List of packages.
|
||||
fixes (Iterable, optional): Iterable of fixes.
|
||||
|
||||
Returns:
|
||||
str: Rendered JSON vulnerabilities report.
|
||||
"""
|
||||
if self.version == "0.5":
|
||||
from safety.formatters.schemas.zero_five import VulnerabilitySchemaV05
|
||||
|
||||
return json_parser.dumps(
|
||||
VulnerabilitySchemaV05().dump(obj=vulnerabilities, many=True), indent=4
|
||||
)
|
||||
|
||||
remediations_recommended = len(remediations.keys())
|
||||
LOG.debug(
|
||||
"Rendering %s vulnerabilities, %s package remediations with full_report: %s",
|
||||
len(vulnerabilities),
|
||||
remediations_recommended,
|
||||
full,
|
||||
)
|
||||
|
||||
report = build_json_report(
|
||||
announcements, vulnerabilities, remediations, packages
|
||||
)
|
||||
template = self.__render_fixes(report, fixes)
|
||||
|
||||
return json_parser.dumps(template, indent=4, cls=SafetyEncoder)
|
||||
|
||||
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> str:
|
||||
"""
|
||||
Render licenses in JSON format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
licenses (List[Dict]): List of licenses.
|
||||
|
||||
Returns:
|
||||
str: Rendered JSON licenses report.
|
||||
"""
|
||||
unique_license_types = set([lic["license"] for lic in licenses])
|
||||
report = get_report_brief_info(
|
||||
as_dict=True, report_type=2, licenses_found=len(unique_license_types)
|
||||
)
|
||||
|
||||
template = {
|
||||
"report_meta": report,
|
||||
"announcements": get_basic_announcements(announcements),
|
||||
"licenses": licenses,
|
||||
}
|
||||
|
||||
return json_parser.dumps(template, indent=4)
|
||||
|
||||
def render_announcements(self, announcements: List[Dict]) -> str:
|
||||
"""
|
||||
Render announcements in JSON format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
|
||||
Returns:
|
||||
str: Rendered JSON announcements.
|
||||
"""
|
||||
return json_parser.dumps(
|
||||
{"announcements": get_basic_announcements(announcements)}, indent=4
|
||||
)
|
||||
|
||||
def __render_fixes(
|
||||
self, scan_template: Dict[str, Any], fixes: Iterable
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Render fixes and update the scan template with remediations information.
|
||||
|
||||
Args:
|
||||
scan_template (Dict[str, Any]): Initial scan template.
|
||||
fixes (Iterable): Iterable of fixes.
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Updated scan template with remediations.
|
||||
"""
|
||||
|
||||
applied = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
|
||||
skipped = defaultdict(lambda: defaultdict(lambda: defaultdict(dict)))
|
||||
|
||||
fixes_applied = []
|
||||
total_applied = 0
|
||||
|
||||
for fix in fixes:
|
||||
if fix.status == "APPLIED":
|
||||
total_applied += 1
|
||||
applied[fix.applied_at][fix.package][fix.previous_spec] = {
|
||||
"previous_version": str(fix.previous_version),
|
||||
"previous_spec": str(fix.previous_spec),
|
||||
"updated_version": str(fix.updated_version),
|
||||
"update_type": str(fix.update_type),
|
||||
"fix_type": fix.fix_type,
|
||||
}
|
||||
fixes_applied.append(fix)
|
||||
else:
|
||||
skipped[fix.applied_at][fix.package][fix.previous_spec] = {
|
||||
"scanned_version": str(fix.previous_version)
|
||||
if fix.previous_version
|
||||
else None,
|
||||
"scanned_spec": str(fix.previous_spec)
|
||||
if fix.previous_spec
|
||||
else None,
|
||||
"skipped_reason": fix.status,
|
||||
}
|
||||
|
||||
vulnerabilities = scan_template.get("vulnerabilities", {})
|
||||
remediation_mode = "NON_INTERACTIVE"
|
||||
|
||||
if SafetyContext().params.get("prompt_mode", False):
|
||||
remediation_mode = "INTERACTIVE"
|
||||
|
||||
scan_template["report_meta"].update(
|
||||
{
|
||||
"remediations_attempted": len(fixes),
|
||||
"remediations_completed": total_applied,
|
||||
"remediation_mode": remediation_mode,
|
||||
}
|
||||
)
|
||||
|
||||
scan_template["remediations_results"] = {
|
||||
"vulnerabilities_fixed": find_vulnerabilities_fixed(
|
||||
vulnerabilities, fixes_applied
|
||||
),
|
||||
"remediations_applied": applied,
|
||||
"remediations_skipped": skipped,
|
||||
}
|
||||
|
||||
return scan_template
|
||||
Reference in New Issue
Block a user