updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,71 @@
|
||||
from collections import namedtuple
|
||||
from typing import List, Dict, Any, Optional, Tuple
|
||||
|
||||
from safety.formatter import FormatterAPI
|
||||
from safety.util import get_basic_announcements
|
||||
|
||||
|
||||
class BareReport(FormatterAPI):
|
||||
"""
|
||||
Bare report, for command line tools.
|
||||
"""
|
||||
|
||||
def render_vulnerabilities(self, announcements: List[Dict[str, Any]], vulnerabilities: List[Any],
|
||||
remediations: Any, full: bool, packages: List[Any], fixes: Tuple = ()) -> str:
|
||||
"""
|
||||
Renders vulnerabilities in a bare format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict[str, Any]]): List of announcements.
|
||||
vulnerabilities (List[Any]): List of vulnerabilities.
|
||||
remediations (Any): Remediation data.
|
||||
full (bool): Flag indicating full output.
|
||||
packages (List[Any]): List of packages.
|
||||
fixes (Tuple, optional): Tuple of fixes.
|
||||
|
||||
Returns:
|
||||
str: Rendered vulnerabilities.
|
||||
"""
|
||||
parsed_announcements = []
|
||||
Announcement = namedtuple("Announcement", ["name"])
|
||||
|
||||
for announcement in get_basic_announcements(announcements, include_local=False):
|
||||
normalized_message = "-".join(announcement.get('message', 'none').lower().split())
|
||||
parsed_announcements.append(Announcement(name=normalized_message))
|
||||
|
||||
announcements_to_render = [announcement.name for announcement in parsed_announcements]
|
||||
affected_packages = list(set([v.package_name for v in vulnerabilities if not v.ignored]))
|
||||
|
||||
return " ".join(announcements_to_render + affected_packages)
|
||||
|
||||
def render_licenses(self, announcements: List[Dict[str, Any]], packages_licenses: List[Dict[str, Any]]) -> str:
|
||||
"""
|
||||
Renders licenses in a bare format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict[str, Any]]): List of announcements.
|
||||
packages_licenses (List[Dict[str, Any]]): List of package licenses.
|
||||
|
||||
Returns:
|
||||
str: Rendered licenses.
|
||||
"""
|
||||
parsed_announcements = []
|
||||
|
||||
for announcement in get_basic_announcements(announcements):
|
||||
normalized_message = "-".join(announcement.get('message', 'none').lower().split())
|
||||
parsed_announcements.append({'license': normalized_message})
|
||||
|
||||
announcements_to_render = [announcement.get('license') for announcement in parsed_announcements]
|
||||
|
||||
licenses = list(set([pkg_li.get('license') for pkg_li in packages_licenses]))
|
||||
sorted_licenses = sorted(licenses)
|
||||
return " ".join(announcements_to_render + sorted_licenses)
|
||||
|
||||
def render_announcements(self, announcements: List[Dict[str, Any]]) -> None:
|
||||
"""
|
||||
Renders announcements in a bare format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict[str, Any]]): List of announcements.
|
||||
"""
|
||||
print('render_announcements bare')
|
||||
@@ -0,0 +1,58 @@
|
||||
import logging
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
|
||||
from safety.formatter import FormatterAPI
|
||||
from safety.formatters.json import build_json_report
|
||||
from safety.output_utils import get_report_brief_info, parse_html
|
||||
from safety.util import get_basic_announcements
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTMLReport(FormatterAPI):
|
||||
"""
|
||||
HTML report formatter for when the output is input for something else.
|
||||
"""
|
||||
|
||||
def render_vulnerabilities(self, announcements: List[Dict], vulnerabilities: List[Dict], remediations: Dict,
|
||||
full: bool, packages: List[Dict], fixes: Tuple = ()) -> Optional[str]:
|
||||
"""
|
||||
Renders vulnerabilities in HTML format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
vulnerabilities (List[Dict]): List of vulnerabilities.
|
||||
remediations (Dict): Remediation data.
|
||||
full (bool): Flag indicating full output.
|
||||
packages (List[Dict]): List of packages.
|
||||
fixes (Tuple, optional): Tuple of fixes.
|
||||
|
||||
Returns:
|
||||
str: Rendered HTML vulnerabilities report.
|
||||
"""
|
||||
LOG.debug(
|
||||
f'HTML Output, Rendering {len(vulnerabilities)} vulnerabilities, {len(remediations)} package '
|
||||
f'remediations with full_report: {full}')
|
||||
report = build_json_report(announcements, vulnerabilities, remediations, packages)
|
||||
|
||||
return parse_html(kwargs={"json_data": report})
|
||||
|
||||
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> None:
|
||||
"""
|
||||
Renders licenses in HTML format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
licenses (List[Dict]): List of licenses.
|
||||
"""
|
||||
pass
|
||||
|
||||
def render_announcements(self, announcements: List[Dict]) -> None:
|
||||
"""
|
||||
Renders announcements in HTML format.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcements.
|
||||
"""
|
||||
pass
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,95 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
from marshmallow import Schema, fields as fields_, post_dump
|
||||
|
||||
|
||||
from importlib.metadata import version as dep_version
|
||||
from packaging import version
|
||||
|
||||
marshmallow_version = version.parse(dep_version("marshmallow"))
|
||||
|
||||
if marshmallow_version >= version.parse("4.0.0"):
|
||||
POST_DUMP_KWARGS = {"pass_collection": True}
|
||||
else:
|
||||
POST_DUMP_KWARGS = {"pass_many": True}
|
||||
|
||||
post_dump_with_kwargs = lambda fn: post_dump(fn, **POST_DUMP_KWARGS) # noqa: E731
|
||||
|
||||
|
||||
class CVSSv2(Schema):
|
||||
"""
|
||||
Schema for CVSSv2 data.
|
||||
|
||||
Attributes:
|
||||
base_score (fields_.Int): Base score of the CVSSv2.
|
||||
impact_score (fields_.Int): Impact score of the CVSSv2.
|
||||
vector_string (fields_.Str): Vector string of the CVSSv2.
|
||||
"""
|
||||
|
||||
base_score = fields_.Int()
|
||||
impact_score = fields_.Int()
|
||||
vector_string = fields_.Str()
|
||||
|
||||
class Meta(Schema.Meta):
|
||||
ordered = True
|
||||
|
||||
|
||||
class CVSSv3(Schema):
|
||||
"""
|
||||
Schema for CVSSv3 data.
|
||||
|
||||
Attributes:
|
||||
base_score (fields_.Int): Base score of the CVSSv3.
|
||||
base_severity (fields_.Str): Base severity of the CVSSv3.
|
||||
impact_score (fields_.Int): Impact score of the CVSSv3.
|
||||
vector_string (fields_.Str): Vector string of the CVSSv3.
|
||||
"""
|
||||
|
||||
base_score = fields_.Int()
|
||||
base_severity = fields_.Str()
|
||||
impact_score = fields_.Int()
|
||||
vector_string = fields_.Str()
|
||||
|
||||
class Meta(Schema.Meta):
|
||||
ordered = True
|
||||
|
||||
|
||||
class VulnerabilitySchemaV05(Schema):
|
||||
"""
|
||||
Legacy JSON report schema used in Safety 1.10.3.
|
||||
|
||||
Attributes:
|
||||
package_name (fields_.Str): Name of the vulnerable package.
|
||||
vulnerable_spec (fields_.Str): Vulnerable specification of the package.
|
||||
version (fields_.Str): Version of the package.
|
||||
advisory (fields_.Str): Advisory details for the vulnerability.
|
||||
vulnerability_id (fields_.Str): ID of the vulnerability.
|
||||
cvssv2 (Optional[CVSSv2]): CVSSv2 details of the vulnerability.
|
||||
cvssv3 (Optional[CVSSv3]): CVSSv3 details of the vulnerability.
|
||||
"""
|
||||
|
||||
package_name = fields_.Str()
|
||||
vulnerable_spec = fields_.Str()
|
||||
version = fields_.Str(attribute="pkg.version")
|
||||
advisory = fields_.Str()
|
||||
vulnerability_id = fields_.Str()
|
||||
cvssv2 = fields_.Nested(CVSSv2, attribute="severity.cvssv2")
|
||||
cvssv3 = fields_.Nested(CVSSv3, attribute="severity.cvssv3")
|
||||
|
||||
class Meta(Schema.Meta):
|
||||
ordered = True
|
||||
|
||||
@post_dump_with_kwargs
|
||||
def wrap_with_envelope(self, data, many, **kwargs) -> List[Tuple]:
|
||||
"""
|
||||
Wraps the dumped data with an envelope.
|
||||
|
||||
Args:
|
||||
data (List[Dict[str, Any]]): The data to be wrapped.
|
||||
many (bool): Indicates if multiple objects are being dumped.
|
||||
**kwargs (Any): Additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
List[Tuple]: The wrapped data.
|
||||
"""
|
||||
return [tuple(d.values()) for d in data]
|
||||
@@ -0,0 +1,199 @@
|
||||
import click
|
||||
|
||||
from safety.formatter import FormatterAPI
|
||||
from safety.output_utils import build_announcements_section_content, format_long_text, \
|
||||
add_empty_line, format_vulnerability, get_final_brief, \
|
||||
build_report_brief_section, format_license, get_final_brief_license, build_remediation_section, \
|
||||
build_primary_announcement, get_specifier_range_info, format_unpinned_vulnerabilities
|
||||
from safety.util import get_primary_announcement, get_basic_announcements, get_terminal_size, \
|
||||
is_ignore_unpinned_mode
|
||||
from collections import defaultdict
|
||||
from typing import List, Dict, Any, Tuple
|
||||
|
||||
class ScreenReport(FormatterAPI):
|
||||
DIVIDER_SECTIONS = '+' + '=' * (get_terminal_size().columns - 2) + '+'
|
||||
|
||||
REPORT_BANNER = DIVIDER_SECTIONS + '\n' + r"""
|
||||
/$$$$$$ /$$
|
||||
/$$__ $$ | $$
|
||||
/$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$
|
||||
/$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$
|
||||
| $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$
|
||||
\____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$
|
||||
/$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$
|
||||
|_______/ \_______/|__/ \_______/ \___/ \____ $$
|
||||
/$$ | $$
|
||||
| $$$$$$/
|
||||
by safetycli.com \______/
|
||||
|
||||
""" + DIVIDER_SECTIONS
|
||||
|
||||
ANNOUNCEMENTS_HEADING = format_long_text(click.style('ANNOUNCEMENTS', bold=True))
|
||||
|
||||
def __build_announcements_section(self, announcements: List[Dict]) -> List[str]:
|
||||
"""
|
||||
Build the announcements section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
|
||||
Returns:
|
||||
List[str]: Formatted announcements section.
|
||||
"""
|
||||
announcements_section = []
|
||||
|
||||
basic_announcements = get_basic_announcements(announcements)
|
||||
|
||||
if basic_announcements:
|
||||
announcements_content = build_announcements_section_content(basic_announcements)
|
||||
announcements_section = [add_empty_line(), self.ANNOUNCEMENTS_HEADING, add_empty_line(),
|
||||
announcements_content, add_empty_line(), self.DIVIDER_SECTIONS]
|
||||
|
||||
return announcements_section
|
||||
|
||||
def render_vulnerabilities(self, announcements: List[Dict], vulnerabilities: List[Dict], remediations: Dict[str, Any],
|
||||
full: bool, packages: List[Dict], fixes: Tuple = ()) -> str:
|
||||
"""
|
||||
Render the vulnerabilities section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
vulnerabilities (List[Dict]): List of vulnerability dictionaries.
|
||||
remediations (Dict[str, Any]): Remediation data.
|
||||
full (bool): Flag indicating full report.
|
||||
packages (List[Dict]): List of package dictionaries.
|
||||
fixes (Tuple, optional): Iterable of fixes.
|
||||
|
||||
Returns:
|
||||
str: Rendered vulnerabilities report.
|
||||
"""
|
||||
announcements_section = self.__build_announcements_section(announcements)
|
||||
primary_announcement = get_primary_announcement(announcements)
|
||||
remediation_section = build_remediation_section(remediations)
|
||||
end_content = []
|
||||
|
||||
if primary_announcement:
|
||||
end_content = [add_empty_line(),
|
||||
build_primary_announcement(primary_announcement, columns=get_terminal_size().columns),
|
||||
self.DIVIDER_SECTIONS]
|
||||
|
||||
table = []
|
||||
ignored = {}
|
||||
total_ignored = 0
|
||||
|
||||
unpinned_packages = defaultdict(list)
|
||||
styled_vulns = []
|
||||
|
||||
for n, vuln in enumerate(vulnerabilities):
|
||||
if vuln.ignored:
|
||||
total_ignored += 1
|
||||
ignored[vuln.package_name] = ignored.get(vuln.package_name, 0) + 1
|
||||
if is_ignore_unpinned_mode(version=vuln.analyzed_version) and not full:
|
||||
unpinned_packages[vuln.package_name].append(vuln)
|
||||
continue
|
||||
styled_vulns.append(format_vulnerability(vuln, full))
|
||||
|
||||
table.extend(format_unpinned_vulnerabilities(unpinned_packages))
|
||||
table.extend(styled_vulns)
|
||||
|
||||
report_brief_section = build_report_brief_section(primary_announcement=primary_announcement, report_type=1,
|
||||
vulnerabilities_found=max(0, len(vulnerabilities)-total_ignored),
|
||||
vulnerabilities_ignored=total_ignored,
|
||||
remediations_recommended=remediations)
|
||||
|
||||
if vulnerabilities:
|
||||
# Add a space between warning and brief, when all the vulnerabilities are ignored.
|
||||
if not styled_vulns:
|
||||
table.append('')
|
||||
|
||||
final_brief = get_final_brief(len(vulnerabilities), remediations, ignored, total_ignored)
|
||||
|
||||
return "\n".join(
|
||||
[ScreenReport.REPORT_BANNER] + announcements_section + [report_brief_section,
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS,
|
||||
format_long_text(
|
||||
click.style('VULNERABILITIES REPORTED',
|
||||
bold=True)),
|
||||
self.DIVIDER_SECTIONS,
|
||||
add_empty_line(),
|
||||
"\n\n".join(table),
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS] +
|
||||
remediation_section + ['', final_brief, '', self.DIVIDER_SECTIONS] + end_content
|
||||
)
|
||||
else:
|
||||
content = format_long_text(click.style("No known security vulnerabilities reported.", bold=True, fg='green'))
|
||||
return "\n".join(
|
||||
[ScreenReport.REPORT_BANNER] + announcements_section + [report_brief_section,
|
||||
self.DIVIDER_SECTIONS,
|
||||
add_empty_line(),
|
||||
content,
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS] +
|
||||
end_content
|
||||
)
|
||||
|
||||
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> str:
|
||||
"""
|
||||
Render the licenses section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
licenses (List[Dict]): List of license dictionaries.
|
||||
|
||||
Returns:
|
||||
str: Rendered licenses report.
|
||||
"""
|
||||
unique_license_types = set([lic['license'] for lic in licenses])
|
||||
|
||||
report_brief_section = build_report_brief_section(primary_announcement=get_primary_announcement(announcements),
|
||||
report_type=2, licenses_found=len(unique_license_types))
|
||||
announcements_section = self.__build_announcements_section(announcements)
|
||||
|
||||
if not licenses:
|
||||
content = format_long_text(click.style("No packages licenses found.", bold=True, fg='red'))
|
||||
return "\n".join(
|
||||
[ScreenReport.REPORT_BANNER] + announcements_section + [report_brief_section,
|
||||
self.DIVIDER_SECTIONS,
|
||||
add_empty_line(),
|
||||
content,
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS]
|
||||
)
|
||||
|
||||
table = []
|
||||
for license in licenses:
|
||||
table.append(format_license(license))
|
||||
|
||||
final_brief = get_final_brief_license(unique_license_types)
|
||||
|
||||
return "\n".join(
|
||||
[ScreenReport.REPORT_BANNER] + announcements_section + [report_brief_section,
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS,
|
||||
format_long_text(
|
||||
click.style('LICENSES FOUND',
|
||||
bold=True, fg='yellow')),
|
||||
self.DIVIDER_SECTIONS,
|
||||
add_empty_line(),
|
||||
"\n".join(table),
|
||||
final_brief,
|
||||
add_empty_line(),
|
||||
self.DIVIDER_SECTIONS]
|
||||
)
|
||||
|
||||
def render_announcements(self, announcements: List[Dict]) -> List[str]:
|
||||
"""
|
||||
Render the announcements section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
|
||||
Returns:
|
||||
str: Rendered announcements section.
|
||||
"""
|
||||
return self.__build_announcements_section(announcements)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
from collections import defaultdict
|
||||
|
||||
import click
|
||||
|
||||
from safety.formatter import FormatterAPI
|
||||
from safety.output_utils import build_announcements_section_content, format_vulnerability, \
|
||||
build_report_brief_section, get_final_brief_license, add_empty_line, get_final_brief, build_remediation_section, \
|
||||
build_primary_announcement, format_unpinned_vulnerabilities
|
||||
from safety.util import get_primary_announcement, get_basic_announcements, is_ignore_unpinned_mode, \
|
||||
get_remediations_count
|
||||
from typing import List, Dict, Tuple, Any
|
||||
|
||||
|
||||
class TextReport(FormatterAPI):
|
||||
"""Basic report, intented to be used for terminals with < 80 columns"""
|
||||
|
||||
SMALL_DIVIDER_SECTIONS = '+' + '=' * 78 + '+'
|
||||
|
||||
TEXT_REPORT_BANNER = SMALL_DIVIDER_SECTIONS + '\n' + r"""
|
||||
/$$$$$$ /$$
|
||||
/$$__ $$ | $$
|
||||
/$$$$$$$ /$$$$$$ | $$ \__//$$$$$$ /$$$$$$ /$$ /$$
|
||||
/$$_____/ |____ $$| $$$$ /$$__ $$|_ $$_/ | $$ | $$
|
||||
| $$$$$$ /$$$$$$$| $$_/ | $$$$$$$$ | $$ | $$ | $$
|
||||
\____ $$ /$$__ $$| $$ | $$_____/ | $$ /$$| $$ | $$
|
||||
/$$$$$$$/| $$$$$$$| $$ | $$$$$$$ | $$$$/| $$$$$$$
|
||||
|_______/ \_______/|__/ \_______/ \___/ \____ $$
|
||||
/$$ | $$
|
||||
| $$$$$$/
|
||||
by safetycli.com \______/
|
||||
|
||||
""" + SMALL_DIVIDER_SECTIONS
|
||||
|
||||
def __build_announcements_section(self, announcements: List[Dict]) -> List[str]:
|
||||
"""
|
||||
Build the announcements section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
|
||||
Returns:
|
||||
List[str]: Formatted announcements section.
|
||||
"""
|
||||
announcements_table = []
|
||||
|
||||
basic_announcements = get_basic_announcements(announcements)
|
||||
|
||||
if basic_announcements:
|
||||
announcements_content = click.unstyle(build_announcements_section_content(basic_announcements,
|
||||
columns=80))
|
||||
announcements_table = [add_empty_line(), ' ANNOUNCEMENTS', add_empty_line(),
|
||||
announcements_content, add_empty_line(), self.SMALL_DIVIDER_SECTIONS]
|
||||
|
||||
return announcements_table
|
||||
|
||||
def render_vulnerabilities(
|
||||
self, announcements: List[Dict], vulnerabilities: List[Dict],
|
||||
remediations: Dict[str, Any], full: bool, packages: List[Dict],
|
||||
fixes: Tuple = ()
|
||||
) -> str:
|
||||
"""
|
||||
Render the vulnerabilities section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
vulnerabilities (List[Dict]): List of vulnerability dictionaries.
|
||||
remediations (Dict[str, Any]): Remediation data.
|
||||
full (bool): Flag indicating full report.
|
||||
packages (List[Dict]): List of package dictionaries.
|
||||
fixes (Tuple, optional): Iterable of fixes.
|
||||
|
||||
Returns:
|
||||
str: Rendered vulnerabilities report.
|
||||
"""
|
||||
primary_announcement = get_primary_announcement(announcements)
|
||||
remediation_section = [click.unstyle(rem) for rem in build_remediation_section(remediations, columns=80)]
|
||||
end_content = []
|
||||
|
||||
if primary_announcement:
|
||||
end_content = [add_empty_line(),
|
||||
build_primary_announcement(primary_announcement, columns=80, only_text=True),
|
||||
self.SMALL_DIVIDER_SECTIONS]
|
||||
|
||||
announcement_section = self.__build_announcements_section(announcements)
|
||||
|
||||
ignored = {}
|
||||
total_ignored = 0
|
||||
unpinned_packages = defaultdict(list)
|
||||
|
||||
raw_vulns = []
|
||||
|
||||
for n, vuln in enumerate(vulnerabilities):
|
||||
if vuln.ignored:
|
||||
total_ignored += 1
|
||||
ignored[vuln.package_name] = ignored.get(vuln.package_name, 0) + 1
|
||||
|
||||
if is_ignore_unpinned_mode(version=vuln.analyzed_version) and not full:
|
||||
unpinned_packages[vuln.package_name].append(vuln)
|
||||
continue
|
||||
|
||||
raw_vulns.append(vuln)
|
||||
|
||||
report_brief_section = click.unstyle(
|
||||
build_report_brief_section(columns=80, primary_announcement=primary_announcement,
|
||||
vulnerabilities_found=max(0, len(vulnerabilities)-total_ignored),
|
||||
vulnerabilities_ignored=total_ignored,
|
||||
remediations_recommended=remediations))
|
||||
|
||||
table = [self.TEXT_REPORT_BANNER] + announcement_section + [
|
||||
report_brief_section,
|
||||
'',
|
||||
self.SMALL_DIVIDER_SECTIONS,
|
||||
]
|
||||
|
||||
if vulnerabilities:
|
||||
table += [" VULNERABILITIES FOUND", self.SMALL_DIVIDER_SECTIONS]
|
||||
|
||||
if unpinned_packages:
|
||||
table.append('')
|
||||
|
||||
table.extend(map(click.unstyle, format_unpinned_vulnerabilities(unpinned_packages, columns=80)))
|
||||
if not raw_vulns:
|
||||
table.append('')
|
||||
|
||||
for vuln in raw_vulns:
|
||||
table.append('\n' + format_vulnerability(vuln, full, only_text=True, columns=80))
|
||||
|
||||
final_brief = click.unstyle(get_final_brief(len(vulnerabilities), remediations, ignored, total_ignored,
|
||||
kwargs={'columns': 80}))
|
||||
table += [add_empty_line(), self.SMALL_DIVIDER_SECTIONS] + remediation_section + ['', final_brief, '', self.SMALL_DIVIDER_SECTIONS] + end_content
|
||||
|
||||
else:
|
||||
table += [add_empty_line(), " No known security vulnerabilities found.", add_empty_line(),
|
||||
self.SMALL_DIVIDER_SECTIONS] + end_content
|
||||
|
||||
return "\n".join(
|
||||
table
|
||||
)
|
||||
|
||||
def render_licenses(self, announcements: List[Dict], licenses: List[Dict]) -> str:
|
||||
"""
|
||||
Render the licenses section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
licenses (List[Dict]): List of license dictionaries.
|
||||
|
||||
Returns:
|
||||
str: Rendered licenses report.
|
||||
"""
|
||||
unique_license_types = set([lic['license'] for lic in licenses])
|
||||
|
||||
report_brief_section = click.unstyle(
|
||||
build_report_brief_section(columns=80, primary_announcement=get_primary_announcement(announcements),
|
||||
licenses_found=len(unique_license_types)))
|
||||
|
||||
packages_licenses = licenses
|
||||
announcements_table = self.__build_announcements_section(announcements)
|
||||
|
||||
final_brief = click.unstyle(
|
||||
get_final_brief_license(unique_license_types, kwargs={'columns': 80}))
|
||||
|
||||
table = [self.TEXT_REPORT_BANNER] + announcements_table + [
|
||||
report_brief_section,
|
||||
self.SMALL_DIVIDER_SECTIONS,
|
||||
" LICENSES",
|
||||
self.SMALL_DIVIDER_SECTIONS,
|
||||
add_empty_line(),
|
||||
]
|
||||
|
||||
if not packages_licenses:
|
||||
table.append(" No packages licenses found.")
|
||||
table += [final_brief, add_empty_line(), self.SMALL_DIVIDER_SECTIONS]
|
||||
|
||||
return "\n".join(table)
|
||||
|
||||
for pkg_license in packages_licenses:
|
||||
text = " {0}, version {1}, license {2}\n".format(pkg_license['package'], pkg_license['version'],
|
||||
pkg_license['license'])
|
||||
table.append(text)
|
||||
|
||||
table += [final_brief, add_empty_line(), self.SMALL_DIVIDER_SECTIONS]
|
||||
|
||||
return "\n".join(table)
|
||||
|
||||
def render_announcements(self, announcements: List[Dict]) -> str:
|
||||
"""
|
||||
Render the announcements section of the report.
|
||||
|
||||
Args:
|
||||
announcements (List[Dict]): List of announcement dictionaries.
|
||||
|
||||
Returns:
|
||||
str: Rendered announcements section.
|
||||
"""
|
||||
rows = self.__build_announcements_section(announcements)
|
||||
rows.insert(0, self.SMALL_DIVIDER_SECTIONS)
|
||||
return '\n'.join(rows)
|
||||
Reference in New Issue
Block a user