Skip to content

Commit

Permalink
Merge pull request #1235 from akto-api-security/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
shivam-rawat-akto authored Jul 2, 2024
2 parents f3f3966 + e59b5b4 commit afb87cc
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 16 deletions.
148 changes: 148 additions & 0 deletions apps/dashboard/src/main/java/com/akto/action/ReportAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.akto.action;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts2.ServletActionContext;
import org.bson.types.ObjectId;
import org.json.JSONObject;

import com.akto.ApiRequest;
import com.akto.TimeoutObject;
import com.akto.dao.context.Context;
import com.akto.dto.User;
import com.akto.log.LoggerMaker;
import com.akto.log.LoggerMaker.LogDb;
import com.akto.utils.Token;
import com.fasterxml.jackson.databind.JsonNode;
import com.twilio.rest.proxy.v1.service.Session;

public class ReportAction extends UserAction {

private String reportId;
private String organizationName;
private String reportDate;
private String reportUrl;
private String pdf;
private String status;

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

public String downloadReportPDF() {
if (reportId == null) {
// Initiate PDF generation

reportId = new ObjectId().toHexString();
loggerMaker.infoAndAddToDb("Triggering pdf download for report id - " + reportId, LogDb.DASHBOARD);

// Make call to puppeteer service
try {
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = request.getSession();
String jsessionId = session.getId();
User user = getSUser();
String accessToken = Token.generateAccessToken(user.getLogin(), "true");

// Set login time if API triggered PDF download
String apiKey = request.getHeader("X-API-KEY");
boolean apiKeyFlag = apiKey != null;
if (apiKeyFlag) {
session.setAttribute("login", Context.now());
}

String url = System.getenv("PUPPETEER_REPLAY_SERVICE_URL") + "/downloadReportPDF";
JSONObject requestBody = new JSONObject();
requestBody.put("reportId", reportId);
requestBody.put("accessToken", accessToken);
requestBody.put("jsessionId", jsessionId);
requestBody.put("organizationName", organizationName);
requestBody.put("reportDate", reportDate);
requestBody.put("reportUrl", reportUrl);
String reqData = requestBody.toString();
JsonNode node = ApiRequest.postRequest(new HashMap<>(), url, reqData);
status = node.get("status").textValue();
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error while triggering pdf download for report id - " + reportId, LogDb.DASHBOARD);
status = "ERROR";
}
} else {
// Check for report completion
loggerMaker.infoAndAddToDb("Polling pdf download status for report id - " + reportId, LogDb.DASHBOARD);

try {
String url = System.getenv("PUPPETEER_REPLAY_SERVICE_URL") + "/downloadReportPDF";
JSONObject requestBody = new JSONObject();
requestBody.put("reportId", reportId);
String reqData = requestBody.toString();
JsonNode node = ApiRequest.postRequest(new HashMap<>(), url, reqData);
status = (String) node.get("status").textValue();
loggerMaker.infoAndAddToDb("Pdf download status for report id - " + reportId + " - " + status, LogDb.DASHBOARD);

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();
}
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error while polling pdf download for report id - " + reportId, LogDb.DASHBOARD);
status = "ERROR";
}
}

return SUCCESS.toUpperCase();
}

public String getReportId() {
return reportId;
}

public void setReportId(String reportId) {
this.reportId = reportId;
}

public String getOrganizationName() {
return organizationName;
}

public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}

public String getReportDate() {
return reportDate;
}

public void setReportDate(String reportDate) {
this.reportDate = reportDate;
}

public String getReportUrl() {
return reportUrl;
}

public void setReportUrl(String reportUrl) {
this.reportUrl = reportUrl;
}

public String getPdf() {
return pdf;
}

public void setPdf(String pdf) {
this.pdf = pdf;
}

public String getStatus() {
return status;
}

public void setStatus(String status) {
this.status = status;
}
}
12 changes: 12 additions & 0 deletions apps/dashboard/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6021,6 +6021,18 @@
</result>
</action>

<action name="api/downloadReportPDF" class="com.akto.action.ReportAction" method="downloadReportPDF">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<result name="SUCCESS" type="json">
</result>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

</package>

</struts>
Original file line number Diff line number Diff line change
Expand Up @@ -399,4 +399,11 @@ export default {
data: {}
})
},
downloadReportPDF(reportId, organizationName, reportDate, reportUrl) {
return request({
url: '/api/downloadReportPDF',
method: 'post',
data: {reportId, organizationName, reportDate, reportUrl}
})
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function VulnerabilityReport() {
const issuesFilter = params.issuesFilter

const [loading, setLoading] = useState(true)
const [pdfDownloadEnabled, setPdfDownloadEnabled] = useState(false)

const [severitiesCount, setSeveritiesCount] = useState({ HIGH: 0, MEDIUM: 0, LOW: 0 });
const [categoryVsIssuesMap, setCategoryVsIssuesMap] = useState({})
Expand Down Expand Up @@ -209,11 +210,94 @@ function VulnerabilityReport() {

useEffect(() => {
setLoading(true)
setPdfDownloadEnabled(false)
fetchVulnerableData()
setLoading(false)
setPdfDownloadEnabled(true)
}, [])



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

let pdfError = ""
let status
let pdf

setPdfDownloadEnabled(false)

const progressToastInterval = setInterval(() => {
func.setToast(true, false, "Report PDF generation in progress. Please wait...")
}, 1000)

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

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)
status = pdfPollResponse?.status

if (status === "COMPLETED") {
pdf = pdfPollResponse?.pdf
break
} else if (status === "ERROR") {
pdfError = "Failed to download PDF"
break
}

await func.sleep(WAIT_DURATION)

func.setToast(true, false, "Report PDF generation in progress. Please wait...")
}
} else {
pdfError = "Failed to start PDF download"
}
} catch (err) {
pdfError = err.message
}

clearInterval(progressToastInterval)

if (status === "COMPLETED") {
if (pdf === undefined) {
pdfError = "Failed to download PDF"
}
else {
// Download the PDF
try {
const byteCharacters = atob(pdf);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: "application/pdf" });
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.setAttribute("download", "akto_security_findings.pdf");
document.body.appendChild(link);
link.click();
func.setToast(true, false, "Report PDF downloaded.")
} catch (err) {
pdfError = err.message
}
}
}

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

setPdfDownloadEnabled(true)
}

const reportSecondaryMenu = (
<div className="report-header-css header-css" id="report-secondary-menu-container">
<Box width="100%">
Expand All @@ -223,7 +307,7 @@ function VulnerabilityReport() {
<Text variant="bodySm">{currentDate}</Text>
</VerticalStack>
<div style={{ display: 'flex', alignItems: 'center' }}>
{/* <Button primary onClick={() => handleDownloadPDF()} disabled={!enableDownload}>Download</Button> */}
<Button primary onClick={() => handleDownloadPF()} disabled={!pdfDownloadEnabled}>Download</Button>
<img src='/public/white_logo.svg' alt="Logo" className='top-bar-logo' />
</div>
</HorizontalStack>
Expand Down Expand Up @@ -403,21 +487,25 @@ function VulnerabilityReport() {
</Box>
<Divider />

{Object.keys(categoryVsIssuesMap).length > 0 && Object.keys(categoryVsIssuesMap).map((categoryName, index) => {
return (
<Box>
<Category
key={index}
index={index}
categoryName={categoryName}
categoryIssues={categoryVsIssuesMap[categoryName]}
categoryMap={categoryMap}
categoryVsApisCountMap={categoryVsApisCountMap}
/>
{index !== categoryVsIssuesMap.length - 1 ? <Divider />: null}
</Box>
)
})}
<Box>
{Object.keys(categoryVsIssuesMap).length > 0 && Object.keys(categoryVsIssuesMap).map((categoryName, index) => {
return (
<Box>
<Category
key={index}
index={index}
categoryName={categoryName}
categoryIssues={categoryVsIssuesMap[categoryName]}
categoryMap={categoryMap}
categoryVsApisCountMap={categoryVsApisCountMap}
/>
{index !== categoryVsIssuesMap.length - 1 ? <Divider />: null}
</Box>
)
})}
<div className="report-page-break"></div>
</Box>


<Box id="akto-approach" paddingBlockStart={6} paddingBlockEnd={8} paddingInlineStart={5} paddingInlineEnd={5}>
<VerticalStack gap="4">
Expand All @@ -441,6 +529,7 @@ function VulnerabilityReport() {

{aktoRecommendations.length !== 0 ?
<Box>
<div className="report-page-break"></div>
<Divider />
<ReportRecommendations
title="Akto recommendations"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.report-page-break {
page-break-before: always;
}

#report-container {
margin: 8px 20vw 0px 20vw;
height: 90vh;
Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/web/polaris_web/web/src/util/func.js
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,9 @@ showConfirmationModal(modalContent, primaryActionContent, primaryAction) {
const year = date.getFullYear();
const daySuffix = func.getDaySuffix(day);
return `${day}${daySuffix} ${month}, ${year}`;
},
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

Expand Down

0 comments on commit afb87cc

Please sign in to comment.