Files
dtrack-defectdojo-automation/src/sbom_dt_dd_api.py
Wolfgang Hottgenroth 7ddb3c153e
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
form for upload, fix
2025-07-15 17:24:34 +02:00

241 lines
7.8 KiB
Python

import os
import json
import yaml
from loguru import logger
from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Request
from fastapi.responses import JSONResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
from converter import minimalSbomFormatConverter
from sbom_dt_dd import generateSBOM, loadToDTrackAndDefectDojo, ApiException
app = FastAPI(
title="SBOM DTrack DefectDojo Synchronization API",
version="0.0.1",
description="",
root_path="/sbom-integrator/v1"
)
config = {}
try:
config['DTRACK_API_URL'] = os.environ["DTRACK_API_URL"]
config['DTRACK_TOKEN'] = os.environ["DTRACK_TOKEN"]
config['DEFECTDOJO_URL'] = os.environ["DEFECTDOJO_URL"]
config['DEFECTDOJO_TOKEN'] = os.environ["DEFECTDOJO_TOKEN"]
config['VERBOSE'] = True
except KeyError as e:
raise Exception(f"Env variable {e} is shall be set")
app.state.config = config
@app.get("/upload-form", response_class=HTMLResponse)
async def upload_form(request: Request):
"""
Route serving an HTML page with the upload form
"""
# BY AWARE OF THE HARDCODED ROOT_PATH BELOW
html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upload Minimal SBOM</title>
</head>
<body>
<h1>Upload Minimal SBOM</h1>
<form id="sbomForm">
<label for="file">Select SBOM file:</label><br>
<input type="file" id="file" name="file" required><br><br>
<label for="reimport">Reimport:</label>
<select name="reimport" id="reimport">
<option value="true">true</option>
<option value="false" selected>false</option>
</select><br><br>
<button type="submit">Upload SBOM</button>
</form>
<div id="result"></div>
<script>
document.getElementById("sbomForm").addEventListener("submit", async function(event) {
event.preventDefault();
let form = document.getElementById("sbomForm");
let formData = new FormData(form);
try {
let response = await fetch("/sbom-integrator/v1/upload-minimal-sbom/", {
method: "POST",
body: formData
});
let resultDiv = document.getElementById("result");
if (response.ok) {
let data = await response.json();
resultDiv.innerHTML = "<p style='color:green;'>Upload successful</p>";
} else {
let errorData = await response.json();
let detail = errorData.detail;
// Dynamisch HTML generieren
let html = "<p style='color:red;'>Upload failed:</p><ul>";
for (const [key, value] of Object.entries(detail)) {
html += "<li style='color:red'><strong>" + key + ":</strong> " + formatValue(value) + "</li>";
}
html += "</ul>";
resultDiv.innerHTML = html;
}
} catch (error) {
console.log(error);
document.getElementById("result").innerHTML = "<p style='color:red;'>Error: " + error + "</p>";
}
});
// Hilfsfunktion für verschachtelte Objekte
function formatValue(value) {
if (typeof value === 'object' && value !== null) {
return "<pre>" + escapeHtml(JSON.stringify(value, null, 2)) + "</pre>";
} else {
return escapeHtml(value);
}
}
function escapeHtml(unsafe) {
if (unsafe === null || unsafe === undefined) {
return '';
}
return String(unsafe)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
</script>
</body>
</html>
"""
return HTMLResponse(content=html_content)
@app.post("/upload-minimal-sbom/")
async def uploadMinimalSBOM(
file: UploadFile = File(...),
reimport: bool = Form(...)
):
"""
Endpoint to upload a minimal SBOM definition
"""
try:
sbom = await file.read()
logger.info("Start converting from minimal format into cyclonedx")
(sbom, projectName, projectVersion, projectClassifier, projectDescription) = minimalSbomFormatConverter(sbom)
logger.info("Converted")
loadToDTrackAndDefectDojo(app.state.config, projectName, projectVersion, projectClassifier, projectDescription, 1, sbom, reimport)
logger.info("Done.")
except yaml.scanner.ScannerError as e:
logger.warning(f"uploadMinimalSBOM, yaml ScannerError: {e.context=}, {e.context_mark=}, {e.problem=}, {e.problem_mark=}, {e.note=}")
raise HTTPException(
status_code=400,
detail={
"error": "yaml ScannerError",
"context": e.context,
"context_mark": str(e.context_mark),
"problem": e.problem,
"problem_mark": str(e.problem_mark),
"note": e.note
}
)
except ApiException as e:
logger.warning(f"uploadMinimalSBOM, ApiException: {type(e.cause)=}, {e.status=}, {e.reason=}, {e.body=}")
raise HTTPException(
status_code=e.status,
detail={
"type": str(type(e.cause)),
"reason": e.reason,
"body": e.body,
"data": e.data
}
)
except Exception as e:
logger.warning(f"uploadMinimalSBOM, Exception: {type(e)=}, {str(e)=}")
raise HTTPException(
status_code=500,
detail={
"error": "Exception occurred",
"type": str(type(e)),
"message": str(e)
}
)
return JSONResponse(content={
"message": "Upload successful!"
})
@app.post("/upload-sbom/")
async def uploadSBOM(
file: UploadFile = File(...),
projectName: str = Form(...),
projectVersion: str = Form(...),
projectClassifier: str = Form(...),
projectDescription: str = Form(...),
reimport: bool = Form(...)
):
"""
Endpoint to upload a CycloneDX SBOM
"""
sbom = await file.read()
try:
sbomJson = json.loads(sbom)
sbom = json.dumps(sbomJson)
loadToDTrackAndDefectDojo(app.state.config, projectName, projectVersion, projectClassifier, projectDescription, 1, str(sbom), reimport)
logger.info("Done.")
except json.decoder.JSONDecodeError as e:
logger.warning(f"uploadSBOM, JSONDecodeError: {e.msg=}")
raise HTTPException(
status_code=400,
detail={
"error": "JSON decoding error",
"msg": e.msg,
"doc": e.doc,
"pos": e.pos,
"lineno": e.lineno,
"colno": e.colno
}
)
except ApiException as e:
logger.warning(f"uploadSBOM, ApiException: {type(e.cause)=}, {e.status=}, {e.reason=}, {e.body=}")
raise HTTPException(
status_code=e.status,
detail={
"type": str(type(e.cause)),
"reason": e.reason,
"body": e.body,
"data": e.data
}
)
except Exception as e:
logger.warning(f"uploadSBOM, Exception: {type(e)=}, {str(e)=}")
raise HTTPException(
status_code=500,
detail={
"error": "Exception occurred",
"type": str(type(e)),
"message": str(e)
}
)
return JSONResponse(content={
"message": "Upload successful!"
})