-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
42b2859
commit bd7472a
Showing
5 changed files
with
255 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ name: Docker | |
|
||
on: | ||
push: | ||
branches: [main] | ||
branches: [convert-docx-to-pdf] | ||
|
||
jobs: | ||
build-backend: | ||
|
@@ -27,16 +27,16 @@ jobs: | |
with: | ||
context: ./backend | ||
push: true | ||
tags: ghcr.io/socfortress/copilot-backend:latest | ||
tags: ghcr.io/socfortress/copilot-backend:lab | ||
build-args: | | ||
COPILOT_API_KEY=${{ secrets.COPILOT_API_KEY }} | ||
- name: Notify Discord | ||
uses: appleboy/[email protected] | ||
with: | ||
webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }} | ||
webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} | ||
message: "Docker image for backend has been updated." | ||
# - name: Notify Discord | ||
# uses: appleboy/[email protected] | ||
# with: | ||
# webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }} | ||
# webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} | ||
# message: "Docker image for backend has been updated." | ||
|
||
build-frontend: | ||
runs-on: ubuntu-latest | ||
|
@@ -60,11 +60,11 @@ jobs: | |
with: | ||
context: ./frontend | ||
push: true | ||
tags: ghcr.io/socfortress/copilot-frontend:latest | ||
tags: ghcr.io/socfortress/copilot-frontend:lab | ||
|
||
- name: Notify Discord | ||
uses: appleboy/[email protected] | ||
with: | ||
webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }} | ||
webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} | ||
message: "Docker image for frontend has been updated." | ||
# - name: Notify Discord | ||
# uses: appleboy/[email protected] | ||
# with: | ||
# webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }} | ||
# webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }} | ||
# message: "Docker image for frontend has been updated." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import os | ||
import pdfkit | ||
from tempfile import NamedTemporaryFile | ||
from typing import Dict, Optional | ||
from jinja2 import Environment, FileSystemLoader | ||
from fastapi.responses import FileResponse | ||
import platform | ||
from app.data_store.data_store_operations import download_data_store | ||
|
||
async def download_template_pdf(template_name: str) -> str: | ||
"""Retrieve the template file content from the data store and save it to a temporary file.""" | ||
template_content = await download_data_store(bucket_name="copilot-case-report-templates", object_name=template_name) | ||
with NamedTemporaryFile(delete=False, suffix=".html") as tmp_template: | ||
tmp_template.write(template_content) | ||
return tmp_template.name | ||
|
||
def create_case_context_pdf(case) -> Dict[str, Dict[str, str]]: | ||
"""Prepare the context for the Jinja template.""" | ||
return { | ||
"case": { | ||
"name": case.case_name, | ||
"description": case.case_description, | ||
"assigned_to": case.assigned_to, | ||
"case_creation_time": case.case_creation_time, | ||
"id": case.id, | ||
"alerts": [ | ||
{ | ||
"alert_name": alert.alert.alert_name, | ||
"alert_description": alert.alert.alert_description, | ||
"status": alert.alert.status, | ||
"tags": [tag.tag.tag for tag in alert.alert.tags], | ||
"assets": [ | ||
{ | ||
"asset_name": asset.asset_name, | ||
"agent_id": asset.agent_id, | ||
} | ||
for asset in alert.alert.assets | ||
], | ||
"comments": [ | ||
{ | ||
"comment": comment.comment, | ||
"user_name": comment.user_name, | ||
"created_at": comment.created_at, | ||
} | ||
for comment in alert.alert.comments | ||
], | ||
"context": { | ||
"source": alert.alert.assets[0].alert_context.source | ||
if alert.alert.assets and alert.alert.assets[0].alert_context | ||
else None, | ||
"context": alert.alert.assets[0].alert_context.context | ||
if alert.alert.assets and alert.alert.assets[0].alert_context | ||
else None, | ||
} | ||
if alert.alert.assets | ||
else None, | ||
"iocs": [ | ||
{ | ||
"ioc_value": ioc.ioc.value, | ||
"ioc_type": ioc.ioc.type, | ||
"ioc_description": ioc.ioc.description, | ||
} | ||
for ioc in alert.alert.iocs | ||
], | ||
} | ||
for alert in case.alerts | ||
], | ||
}, | ||
} | ||
|
||
def render_html_template(template_path: str, context: Dict[str, Dict[str, str]]) -> str: | ||
"""Render the Jinja HTML template with the provided context.""" | ||
template_dir = os.path.dirname(template_path) | ||
template_name = os.path.basename(template_path) | ||
|
||
env = Environment(loader=FileSystemLoader(template_dir)) | ||
template = env.get_template(template_name) | ||
rendered_html = template.render(context) | ||
|
||
# Save rendered HTML to a temporary file | ||
with NamedTemporaryFile(delete=False, suffix=".html") as tmp: | ||
tmp.write(rendered_html.encode("utf-8")) | ||
return tmp.name | ||
|
||
def convert_html_to_pdf(html_path: str) -> str: | ||
"""Convert the HTML file to a PDF using wkhtmltopdf via pdfkit, with dynamic path detection for different platforms.""" | ||
pdf_path = html_path.replace(".html", ".pdf") | ||
wkhtmltopdf_paths = [] | ||
|
||
# Determine paths to wkhtmltopdf based on the current platform | ||
try: | ||
if platform.system() == "Windows": | ||
# Common installation paths for wkhtmltopdf on Windows | ||
wkhtmltopdf_paths = [ | ||
r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe", | ||
r"C:\Program Files (x86)\wkhtmltopdf\bin\wkhtmltopdf.exe" | ||
] | ||
elif platform.system() == "Darwin": # macOS | ||
# Common installation paths for wkhtmltopdf on macOS | ||
wkhtmltopdf_paths = [ | ||
"/usr/local/bin/wkhtmltopdf", | ||
"/opt/homebrew/bin/wkhtmltopdf" # For macOS ARM (M1/M2) using Homebrew | ||
] | ||
elif platform.system() == "Linux": | ||
# Common installation paths for wkhtmltopdf on Linux (Debian-based) | ||
wkhtmltopdf_paths = [ | ||
"/usr/bin/wkhtmltopdf", | ||
"/usr/local/bin/wkhtmltopdf" | ||
] | ||
|
||
# Try each path until a valid executable is found | ||
path_to_wkhtmltopdf = None | ||
for path in wkhtmltopdf_paths: | ||
try: | ||
# Check if the executable can be accessed | ||
config = pdfkit.configuration(wkhtmltopdf=path) | ||
path_to_wkhtmltopdf = path | ||
break | ||
except OSError: | ||
continue | ||
|
||
# Raise an exception if no valid wkhtmltopdf path is found | ||
if path_to_wkhtmltopdf is None: | ||
raise FileNotFoundError("No valid wkhtmltopdf executable found. Ensure wkhtmltopdf is installed and accessible.") | ||
|
||
# Generate the PDF from HTML using the valid wkhtmltopdf path | ||
pdfkit.from_file(html_path, pdf_path, configuration=config) | ||
except Exception as e: | ||
raise RuntimeError(f"Failed to convert HTML to PDF: {str(e)}") | ||
|
||
return pdf_path | ||
|
||
def create_file_response_pdf(file_path: str, file_name: Optional[str] = "case_report.pdf") -> FileResponse: | ||
"""Create a FileResponse object for the rendered document.""" | ||
return FileResponse( | ||
file_path, | ||
filename=file_name, | ||
media_type="application/pdf", | ||
) | ||
|
||
def cleanup_temp_files(file_paths: list): | ||
"""Clean up the temporary files.""" | ||
for file_path in file_paths: | ||
if os.path.exists(file_path): | ||
os.remove(file_path) |
50 changes: 50 additions & 0 deletions
50
backend/app/incidents/templates/case_report_jinja_template.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Case Report</title> | ||
<style> | ||
body { font-family: Arial, sans-serif; } | ||
.case-info { margin-bottom: 20px; } | ||
.alert { margin-bottom: 15px; } | ||
</style> | ||
</head> | ||
<body> | ||
<h1>Case Report</h1> | ||
<div class="case-info"> | ||
<p><strong>Name of Case:</strong> {{ case.name }}</p> | ||
<p><strong>Description:</strong> {{ case.description }}</p> | ||
<p><strong>Assigned To:</strong> {{ case.assigned_to }}</p> | ||
<p><strong>Case Creation Time:</strong> {{ case.case_creation_time }}</p> | ||
<p><strong>Case ID:</strong> {{ case.id }}</p> | ||
</div> | ||
|
||
<h2>Alerts:</h2> | ||
{% for alert in case.alerts %} | ||
<div class="alert"> | ||
<p><strong>Alert Name:</strong> {{ alert.alert_name }}</p> | ||
<p><strong>Description:</strong> {{ alert.alert_description }}</p> | ||
<p><strong>Status:</strong> {{ alert.status }}</p> | ||
<p><strong>Tags:</strong> {{ alert.tags | join(', ') }}</p> | ||
|
||
<h3>Assets:</h3> | ||
{% for asset in alert.assets %} | ||
<p>- <strong>Asset Name:</strong> {{ asset.asset_name }} | <strong>Agent ID:</strong> {{ asset.agent_id }}</p> | ||
{% endfor %} | ||
|
||
<h3>Comments:</h3> | ||
{% for comment in alert.comments %} | ||
<p>- "{{ comment.comment }}" by {{ comment.user_name }} at {{ comment.created_at }}</p> | ||
{% endfor %} | ||
|
||
<h3>Context:</h3> | ||
<p><strong>Source:</strong> {{ alert.context.source }}</p> | ||
<p><strong>Context Details:</strong> {{ alert.context.context }}</p> | ||
|
||
<h3>IoCs:</h3> | ||
{% for ioc in alert.iocs %} | ||
<p>- <strong>IoC Value:</strong> {{ ioc.ioc_value }} | <strong>Type:</strong> {{ ioc.ioc_type }} | <strong>Description:</strong> {{ ioc.ioc_description }}</p> | ||
{% endfor %} | ||
</div> | ||
{% endfor %} | ||
</body> | ||
</html> |