Skip to content

Commit

Permalink
Merge pull request #1952 from akto-api-security/feature/storing_vul_r…
Browse files Browse the repository at this point in the history
…eport_pdf_in_db

feat: storing vul report pdf in db
  • Loading branch information
Ark2307 authored Jan 14, 2025
2 parents 407b3ac + 87bc3bb commit 9ed4ed3
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 16 deletions.
63 changes: 62 additions & 1 deletion apps/dashboard/src/main/java/com/akto/action/ReportAction.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.akto.action;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
Expand All @@ -10,6 +11,11 @@
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.akto.dao.testing.sources.TestReportsDao;
import com.akto.dto.testing.sources.TestReports;
import com.mongodb.MongoCommandException;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import org.apache.struts2.ServletActionContext;
import org.bson.types.ObjectId;
import org.json.JSONObject;
Expand All @@ -32,10 +38,45 @@ public class ReportAction extends UserAction {
private String reportUrl;
private String pdf;
private String status;
private boolean firstPollRequest;

private static final LoggerMaker loggerMaker = new LoggerMaker(ReportAction.class);

public String downloadReportPDF() {
if(reportUrl == null || reportUrl.isEmpty()) {
status = "ERROR";
addActionError("Report URL cannot be empty");
return ERROR.toUpperCase();
}

String reportUrlId;
try {
String path = new URL(reportUrl).getPath();
String[] segments = path.split("/");
reportUrlId = segments[segments.length - 1];
} catch (Exception e) {
status = "ERROR";
addActionError("Report URL cannot be empty");
return ERROR.toUpperCase();
}

if(!ObjectId.isValid(reportUrlId)) {
status = "ERROR";
addActionError("Report URL is invalid");
return ERROR.toUpperCase();
}

ObjectId reportUrlIdObj = new ObjectId(reportUrlId);

if(firstPollRequest) {
TestReports testReport = TestReportsDao.instance.findOne(Filters.eq("_id", reportUrlIdObj));
if(testReport != null && (testReport.getPdfReportString() != null && !testReport.getPdfReportString().isEmpty())) {
status = "COMPLETED";
pdf = testReport.getPdfReportString();
return SUCCESS.toUpperCase();
}
}

if (reportId == null) {
// Initiate PDF generation

Expand Down Expand Up @@ -89,6 +130,22 @@ public String downloadReportPDF() {
if (status.equals("COMPLETED")) {
loggerMaker.infoAndAddToDb("Pdf download status for report id - " + reportId + " completed. Attaching pdf in response ", LogDb.DASHBOARD);
pdf = node.get("base64PDF").textValue();
try {
TestReportsDao.instance.updateOne(Filters.eq("_id", reportUrlIdObj), Updates.set(TestReports.PDF_REPORT_STRING, pdf));
} catch(Exception e) {
loggerMaker.errorAndAddToDb("Error: " + e.getMessage() + ", while updating report binary for reportId: " + reportId, LogDb.DASHBOARD);
if (e instanceof MongoCommandException) {
MongoCommandException mongoException = (MongoCommandException) e;
if (mongoException.getCode() == 17420) {
addActionError("The report is too large to save. Please reduce its size and try again.");
} else {
addActionError("A database error occurred while saving the report. Try again later.");
}
} else {
addActionError("An error occurred while updating the report in DB. Please try again.");
}
status = "ERROR";
}
}
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error while polling pdf download for report id - " + reportId, LogDb.DASHBOARD);
Expand Down Expand Up @@ -146,4 +203,8 @@ public String getStatus() {
public void setStatus(String status) {
this.status = status;
}

public void setFirstPollRequest(boolean firstPollRequest) {
this.firstPollRequest = firstPollRequest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,11 @@ export default {
data: {}
})
},
downloadReportPDF(reportId, organizationName, reportDate, reportUrl) {
downloadReportPDF(reportId, organizationName, reportDate, reportUrl, firstPollRequest) {
return request({
url: '/api/downloadReportPDF',
method: 'post',
data: {reportId, organizationName, reportDate, reportUrl}
data: {reportId, organizationName, reportDate, reportUrl, firstPollRequest}
})
},
fetchScript() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint-disable no-loop-func */
import { Box, Divider, Frame, HorizontalStack, Text, TopBar, VerticalStack } from '@shopify/polaris'
import { Box, Button, Divider, Frame, HorizontalStack, Text, TopBar, VerticalStack } from '@shopify/polaris'
import React, { useEffect, useRef } from 'react'
import api from '../api'
import issuesApi from '@/apps/dashboard/pages/issues/api'
Expand Down Expand Up @@ -233,7 +233,7 @@ const VulnerabilityReport = () => {


const handleDownloadPF = async () => {
const WAIT_DURATION = 2500, MAX_RETRIES = 15
const WAIT_DURATION = 5000, MAX_RETRIES = 60
const reportUrl = window.location.href

let pdfError = ""
Expand All @@ -242,20 +242,32 @@ const VulnerabilityReport = () => {

setPdfDownloadEnabled(false)

const progressToastInterval = setInterval(() => {
func.setToast(true, false, "Report PDF generation in progress. Please wait...")
let reportToastInterval = setInterval(() => {
func.setToast(true, false, "Preparing your report. This might take a moment...")
}, 1000)

let generationStarted = false
setTimeout(() => {
clearInterval(reportToastInterval)
generationStarted = true
if(status === "IN_PROGRESS") {
reportToastInterval = setInterval(() => {
func.setToast(true, false, "Report PDF generation in progress. Please wait...")
}, 1000)
}
}, 6000)

try {
// Trigger pdf download
const startDownloadReponse = await api.downloadReportPDF(null, organizationName, currentDate, reportUrl)
const startDownloadReponse = await api.downloadReportPDF(null, organizationName, currentDate, reportUrl, true)
const reportId = startDownloadReponse?.reportId
status = startDownloadReponse?.status
pdf = startDownloadReponse?.pdf

if (reportId !== null && status === "IN_PROGRESS") {
// Poll for PDF completion
for(let i = 0; i < MAX_RETRIES; i++) {
const pdfPollResponse = await api.downloadReportPDF(reportId, organizationName, currentDate, reportUrl)
const pdfPollResponse = await api.downloadReportPDF(reportId, organizationName, currentDate, reportUrl, false)
status = pdfPollResponse?.status

if (status === "COMPLETED") {
Expand All @@ -268,16 +280,22 @@ const VulnerabilityReport = () => {

await func.sleep(WAIT_DURATION)

func.setToast(true, false, "Report PDF generation in progress. Please wait...")
func.setToast(generationStarted, false, "Report PDF generation in progress. Please wait...")

if(i === MAX_RETRIES - 1) {
pdfError = "Failed to download PDF. The size might be too large. Filter out unnecessary issues and try again."
}
}
} else {
pdfError = "Failed to start PDF download"
if(status !== "COMPLETED") {
pdfError = "Failed to start PDF download"
}
}
} catch (err) {
pdfError = err.message
pdfError = err?.response?.data?.actionErrors?.[0] || err.message
}

clearInterval(progressToastInterval)
clearInterval(reportToastInterval)

if (status === "COMPLETED") {
if (pdf === undefined) {
Expand All @@ -300,13 +318,13 @@ const VulnerabilityReport = () => {
link.click();
func.setToast(true, false, "Report PDF downloaded.")
} catch (err) {
pdfError = err.message
pdfError = err?.response?.data?.actionErrors?.[0] || err.message
}
}
}

if (pdfError !== "") {
func.setToast(true, true, `Error while downloading PDF. Please try again. \nError: ${pdfError}`)
func.setToast(true, true, `Error: ${pdfError}`)
}

setPdfDownloadEnabled(true)
Expand All @@ -321,7 +339,9 @@ const VulnerabilityReport = () => {
<Text variant="bodySm">{currentDate}</Text>
</VerticalStack>
<div style={{ display: 'flex', alignItems: 'center' }}>
{/* <Button primary onClick={() => handleDownloadPF()} disabled={!pdfDownloadEnabled}>Download</Button> */}
{
(window.USER_NAME?.toLowerCase()?.includes("@akto.io") || window.ACCOUNT_NAME?.toLowerCase()?.includes("advanced bank")) ? <Button primary onClick={() => handleDownloadPF()} disabled={!pdfDownloadEnabled}>Download</Button> : <></>
}
<img src='/public/white_logo.svg' alt="Logo" className='top-bar-logo' />
</div>
</HorizontalStack>
Expand Down

0 comments on commit 9ed4ed3

Please sign in to comment.