////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2009-2013 Denim Group, Ltd.
//
// The contents of this file are subject to the Mozilla Public License
// Version 2.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is ThreadFix.
//
// The Initial Developer of the Original Code is Denim Group, Ltd.
// Portions created by Denim Group, Ltd. are Copyright (C)
// Denim Group, Ltd. All Rights Reserved.
//
// Contributor(s): Denim Group, Ltd.
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.webapp.controller;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.support.SessionStatus;
import com.denimgroup.threadfix.data.entities.Application;
import com.denimgroup.threadfix.data.entities.Organization;
import com.denimgroup.threadfix.data.entities.ReportParameters;
import com.denimgroup.threadfix.data.entities.ReportParameters.ReportFormat;
import com.denimgroup.threadfix.service.OrganizationService;
import com.denimgroup.threadfix.service.PermissionService;
import com.denimgroup.threadfix.service.SanitizedLogger;
import com.denimgroup.threadfix.service.VulnerabilityService;
import com.denimgroup.threadfix.service.report.ReportsService;
import com.denimgroup.threadfix.service.report.ReportsService.ReportCheckResult;
@Controller
@RequestMapping("/reports")
@PreAuthorize("hasRole('ROLE_CAN_GENERATE_REPORTS')")
public class ReportsController {
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
private static final String RANDOM_PROVIDER = "SUN";
private final SanitizedLogger log = new SanitizedLogger(ReportsController.class);
private OrganizationService organizationService;
private PermissionService permissionService;
private ReportsService reportsService;
private VulnerabilityService vulnerabilityService;
private SecureRandom random;
@Autowired
public ReportsController(OrganizationService organizationService,
VulnerabilityService vulnerabilityService,
PermissionService permissionService,
ReportsService reportsService) {
this.organizationService = organizationService;
this.permissionService = permissionService;
this.vulnerabilityService = vulnerabilityService;
this.reportsService = reportsService;
}
public ReportsController(){}
@ModelAttribute("organizationList")
public List<Organization> getOrganizations() {
List<Organization> organizationList = organizationService.loadAllActiveFilter();
List<Organization> returnList = new ArrayList<Organization>();
for (Organization org : organizationList) {
List<Application> validApps = permissionService.filterApps(org);
if (validApps != null && !validApps.isEmpty()) {
org.setActiveApplications(validApps);
returnList.add(org);
}
}
return returnList;
}
@RequestMapping(method = RequestMethod.GET)
public String index(Model model, HttpServletRequest request) {
model.addAttribute("hasVulnerabilities", vulnerabilityService.activeVulnerabilitiesExist());
model.addAttribute("reportParameters", new ReportParameters());
model.addAttribute("error", ControllerUtils.getErrorMessage(request));
model.addAttribute("firstReport", ControllerUtils.getItem(request, "reportId"));
model.addAttribute("firstTeamId", ControllerUtils.getItem(request, "teamId"));
model.addAttribute("firstAppId", ControllerUtils.getItem(request, "appId"));
return "reports/index";
}
@RequestMapping(value="/{reportId}", method = RequestMethod.GET)
public String toReport(@PathVariable("reportId") int reportId,
Model model, HttpServletRequest request) {
ControllerUtils.addItem(request, "reportId", reportId);
return "redirect:/reports";
}
@RequestMapping(value="/{reportId}/{teamId}", method = RequestMethod.GET)
public String toReport(@PathVariable("reportId") int reportId,
@PathVariable("teamId") int teamId,
Model model, HttpServletRequest request) {
ControllerUtils.addItem(request, "reportId", reportId);
ControllerUtils.addItem(request, "teamId", teamId);
return "redirect:/reports";
}
@RequestMapping(value="/{reportId}/{teamId}/{appId}", method = RequestMethod.GET)
public String toReport(@PathVariable("reportId") int reportId,
@PathVariable("teamId") int teamId,
@PathVariable("appId") int appId,
Model model, HttpServletRequest request) {
ControllerUtils.addItem(request, "reportId", reportId);
ControllerUtils.addItem(request, "teamId", teamId);
ControllerUtils.addItem(request, "appId", appId);
return "redirect:/reports";
}
@RequestMapping(value="/ajax/export", method = RequestMethod.POST)
public String processExportRequest(Model model, @ModelAttribute ReportParameters reportParameters,
BindingResult result, SessionStatus status, HttpServletRequest request,
HttpServletResponse response) throws IOException {
ReportCheckResultBean reportCheckResultBean = reportsService.generateReport(reportParameters,
request);
ReportCheckResult reportCheckResult = null;
if (reportCheckResultBean != null) {
reportCheckResult = reportCheckResultBean.getReportCheckResult();
} else {
reportCheckResult = ReportCheckResult.OTHER_ERROR;
}
if (reportCheckResult == ReportCheckResult.VALID) {
boolean isPdf = reportParameters.getFormatId() == 3;
String fileName = "report";
InputStream in = null;
if (isPdf) {
response.setContentType("application/pdf");
fileName = "report_pdf.pdf";
if (reportCheckResultBean.getReportBytes() != null) {
in = new ByteArrayInputStream(reportCheckResultBean.getReportBytes());
}
} else {
response.setContentType("application/octet-stream");
fileName = "report_csv.csv";
StringBuffer report = reportCheckResultBean.getReport();
if (report != null) {
String pageString = report.toString();
if (pageString != null) {
in = new ByteArrayInputStream(pageString.getBytes("UTF-8"));
}
}
}
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
if (in != null) {
ServletOutputStream out = response.getOutputStream();
byte[] outputByteBuffer = new byte[65535];
int remainingSize = in.read(outputByteBuffer, 0, 65535);
// copy binary content to output stream
while (remainingSize != -1) {
out.write(outputByteBuffer, 0, remainingSize);
remainingSize = in.read(outputByteBuffer, 0, 65535);
}
in.close();
out.flush();
out.close();
return null;
} else {
log.warn("Unable to find data for report.");
return returnError(request, model, ReportCheckResult.OTHER_ERROR);
}
}
return returnError(request, model, reportCheckResult);
}
@RequestMapping(value="/ajax", method = RequestMethod.POST)
public String processSubmit(Model model, @ModelAttribute ReportParameters reportParameters,
BindingResult result, SessionStatus status, HttpServletRequest request,
HttpServletResponse response) throws IOException {
// reroute if it's scanner comparison or portfolio report
if (reportParameters.getReportFormat() == ReportFormat.CHANNEL_COMPARISON_DETAIL) {
return reportsService.scannerComparisonByVulnerability(model, reportParameters);
} else if (reportParameters.getReportFormat() == ReportFormat.PORTFOLIO_REPORT) {
return new PortfolioReportController(organizationService).index(
model, request, reportParameters.getOrganizationId());
}
if (reportParameters.getFormatId() != 1) {
return processExportRequest(model, reportParameters, result, status, request, response);
}
ReportCheckResultBean reportCheckResultBean = reportsService.generateReport(reportParameters,
request);
ReportCheckResult reportCheckResult = reportCheckResultBean.getReportCheckResult();
if (reportCheckResult == ReportCheckResult.VALID) {
boolean csvEnabled = !(reportParameters.getReportFormat() == ReportFormat.TRENDING ||
reportParameters.getReportFormat() == ReportFormat.MONTHLY_PROGRESS_REPORT);
StringBuffer report = reportCheckResultBean.getReport();
if (report != null) {
log.info("Finished generating report.");
model.addAttribute("jasperReport", addParameterToReport(report));
model.addAttribute("csvEnabled", csvEnabled);
model.addAttribute("pdfEnabled", true);
model.addAttribute("reportId",reportParameters.getReportId());
return "reports/report";
} else {
log.warn("Failed to generate report.");
ControllerUtils.addErrorMessage(request, "There was an error generating the report.");
model.addAttribute("contentPage", "/reports");
return "ajaxRedirectHarness";
}
} else {
return returnError(request, model, reportCheckResult);
}
}
// TODO - Move the creation of SecureRandoms into some sort of shared facility
// for the entire application (each class doesn't need to repeat this code)
private SecureRandom getRandomSource() {
if (this.random == null) {
try {
this.random = SecureRandom.getInstance(RANDOM_ALGORITHM, RANDOM_PROVIDER);
} catch (NoSuchAlgorithmException e) {
log.error("Unable to find algorithm " + RANDOM_ALGORITHM, e);
} catch (NoSuchProviderException e) {
log.error("Unable to find provider " + RANDOM_PROVIDER, e);
}
}
return(this.random);
}
private String addParameterToReport(StringBuffer buffer) {
String resultString = buffer.toString();
String regex = "(.*<img [^>]*img_[^\"]*)(.*)";
return resultString.replaceAll(regex, "$1?" + getRandomSource().nextInt() + "$2");
}
private String returnError(HttpServletRequest request, Model model,
ReportCheckResult reportCheckResult) {
if (reportCheckResult == ReportCheckResult.BAD_REPORT_TYPE) {
return incorrectReportIdError(request, model);
} else if (reportCheckResult == ReportCheckResult.NO_APPLICATIONS) {
return missingApplicationsError(request, model);
} else {
return exceptionError(request, model);
}
}
private String incorrectReportIdError(HttpServletRequest request, Model model) {
log.warn("An incorrect report ID was passed through, returning an error page.");
ControllerUtils.addErrorMessage(request, "An invalid report type was chosen.");
return redirect(model);
}
private String missingApplicationsError(HttpServletRequest request, Model model) {
ControllerUtils.addErrorMessage(request, "You must select at least one application.");
return redirect(model);
}
private String exceptionError(HttpServletRequest request, Model model) {
ControllerUtils.addErrorMessage(request, "An error occurred while generating the report. " +
"Check the logs for more details.");
return redirect(model);
}
private String redirect(Model model) {
model.addAttribute("contentPage", "/reports");
return "ajaxRedirectHarness";
}
}