"""Reporting tools for generating vulnerability reports."""
import json
from typing import Dict, Any, List, Optional
from datetime import datetime
import logging
from ..models import ScanResult, Finding, Severity
from ..config import ConfigManager
from ..storage.database import DatabaseManager
logger = logging.getLogger(__name__)
class ReportingTools:
"""Tools for generating reports."""
def __init__(self, config: ConfigManager, db: DatabaseManager):
"""Initialize reporting tools.
Args:
config: Configuration manager
db: Database manager
"""
self.config = config
self.db = db
async def generate_report(
self,
program_id: str,
scan_ids: List[str],
format: str = "markdown",
) -> Dict[str, Any]:
"""Generate a vulnerability report.
Args:
program_id: Program identifier
scan_ids: List of scan IDs to include
format: Output format (markdown/json)
Returns:
Dictionary with report content
"""
program = self.config.get_program(program_id)
if not program:
return {'success': False, 'error': f"Program '{program_id}' not found"}
# Gather scan results
scans = []
all_findings = []
for scan_id in scan_ids:
scan = self.db.get_scan_result(scan_id)
if scan:
scans.append(scan)
all_findings.extend(scan.findings)
if not scans:
return {
'success': False,
'error': 'No scan results found for provided IDs'
}
# Generate report based on format
if format == "markdown":
report = self._generate_markdown_report(program, scans, all_findings)
elif format == "json":
report = self._generate_json_report(program, scans, all_findings)
else:
return {'success': False, 'error': f"Unsupported format: {format}"}
return {
'success': True,
'program_id': program_id,
'format': format,
'scans_included': len(scans),
'total_findings': len(all_findings),
'report': report,
}
def _generate_markdown_report(
self,
program,
scans: List[ScanResult],
findings: List[Finding],
) -> str:
"""Generate Markdown format report."""
# Count findings by severity
severity_counts = {
Severity.CRITICAL: 0,
Severity.HIGH: 0,
Severity.MEDIUM: 0,
Severity.LOW: 0,
Severity.INFO: 0,
}
for finding in findings:
severity_counts[finding.severity] += 1
report = f"""# Vulnerability Report: {program.name}
**Program ID:** {program.program_id}
**Platform:** {program.platform}
**Generated:** {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC
## Executive Summary
This report contains findings from {len(scans)} security scans performed on targets within the scope of {program.name}.
### Findings Summary
- **Critical:** {severity_counts[Severity.CRITICAL]}
- **High:** {severity_counts[Severity.HIGH]}
- **Medium:** {severity_counts[Severity.MEDIUM]}
- **Low:** {severity_counts[Severity.LOW]}
- **Info:** {severity_counts[Severity.INFO]}
**Total Findings:** {len(findings)}
## Scans Performed
"""
for scan in scans:
report += f"- **{scan.tool}** on `{scan.target}` ({scan.timestamp.strftime('%Y-%m-%d %H:%M')})\n"
report += "\n## Detailed Findings\n\n"
# Group findings by severity
for severity in [Severity.CRITICAL, Severity.HIGH, Severity.MEDIUM, Severity.LOW, Severity.INFO]:
severity_findings = [f for f in findings if f.severity == severity]
if severity_findings:
report += f"\n### {severity.upper()} Severity\n\n"
for i, finding in enumerate(severity_findings, 1):
report += f"#### {i}. {finding.title}\n\n"
report += f"**Severity:** {finding.severity.upper()}\n\n"
if finding.cvss_score:
report += f"**CVSS Score:** {finding.cvss_score}\n\n"
if finding.cwe_id:
report += f"**CWE:** {finding.cwe_id}\n\n"
report += f"**Description:**\n{finding.description}\n\n"
if finding.evidence:
report += "**Evidence:**\n```json\n"
report += json.dumps(finding.evidence, indent=2)
report += "\n```\n\n"
if finding.remediation:
report += f"**Remediation:**\n{finding.remediation}\n\n"
report += "---\n\n"
return report
def _generate_json_report(
self,
program,
scans: List[ScanResult],
findings: List[Finding],
) -> str:
"""Generate JSON format report."""
report_data = {
'program': {
'id': program.program_id,
'name': program.name,
'platform': program.platform,
},
'generated_at': datetime.utcnow().isoformat(),
'scans': [
{
'scan_id': scan.scan_id,
'tool': scan.tool,
'target': scan.target,
'timestamp': scan.timestamp.isoformat(),
'status': scan.status,
}
for scan in scans
],
'findings': [
{
'title': f.title,
'severity': f.severity,
'description': f.description,
'cvss_score': f.cvss_score,
'cwe_id': f.cwe_id,
'evidence': f.evidence,
'remediation': f.remediation,
'confirmed': f.confirmed,
}
for f in findings
],
'summary': {
'total_scans': len(scans),
'total_findings': len(findings),
'critical': len([f for f in findings if f.severity == Severity.CRITICAL]),
'high': len([f for f in findings if f.severity == Severity.HIGH]),
'medium': len([f for f in findings if f.severity == Severity.MEDIUM]),
'low': len([f for f in findings if f.severity == Severity.LOW]),
'info': len([f for f in findings if f.severity == Severity.INFO]),
}
}
return json.dumps(report_data, indent=2)
async def export_findings(
self,
scan_id: str,
format: str = "json",
) -> Dict[str, Any]:
"""Export findings from a specific scan.
Args:
scan_id: Scan ID
format: Export format (json/csv)
Returns:
Dictionary with exported data
"""
scan = self.db.get_scan_result(scan_id)
if not scan:
return {'success': False, 'error': f"Scan '{scan_id}' not found"}
if format == "json":
export_data = {
'scan_id': scan.scan_id,
'tool': scan.tool,
'target': scan.target,
'timestamp': scan.timestamp.isoformat(),
'findings': [
{
'title': f.title,
'severity': f.severity,
'description': f.description,
'cvss_score': f.cvss_score,
'cwe_id': f.cwe_id,
'evidence': f.evidence,
}
for f in scan.findings
]
}
return {
'success': True,
'format': format,
'data': json.dumps(export_data, indent=2),
}
return {'success': False, 'error': f"Unsupported format: {format}"}