Added more fail safety for vulnerability export.

This should affect all exports when a vulnerability disappears.
This commit is contained in:
Šesták Vít
2017-10-11 16:54:25 +02:00
parent cdb31dcc4e
commit 2a95b07b54
6 changed files with 72 additions and 59 deletions

View File

@@ -8,7 +8,7 @@ import com.ysoft.html.HtmlWithText._
import com.ysoft.odc.{Absolutizer, SetDiff}
import controllers._
import models.Change.Direction
import models.{Change, EmailMessageId}
import models.{Change, EmailMessageId, VulnerabilityOverview}
import play.api.libs.mailer.{Email, MailerClient}
import play.twirl.api.{Html, HtmlFormat}
@@ -19,50 +19,11 @@ object EmailExportType extends Enumeration {
val Digest = Value("digest")
}
object EmailExportService {
private object VulnerabilityDescription{
def apply(name: String, v: Option[Vulnerability]): VulnerabilityDescription = v.fold(UnknownVulnerabilityDescription(name))(new StandardVulnerabilityDescription(_))
}
private abstract class VulnerabilityDescription {
def name: String
def description: String
def cvssScore: Option[Double]
}
private final class StandardVulnerabilityDescription(vulnerability: Vulnerability) extends VulnerabilityDescription {
override def name: String = vulnerability.name
override def description: String = vulnerability.description
override def cvssScore: Option[Double] = vulnerability.cvssScore
}
private final class UnknownVulnerabilityDescription(override val name: String, link: String) extends VulnerabilityDescription {
override def description: String = s"Unknown vulnerability. Try looking at the following address for more details: $link"
override def cvssScore: Option[Double] = None
}
private final class TotallyUnknownVulnerabilityDescription(override val name: String) extends VulnerabilityDescription {
override def description: String = s"Unknown vulnerability. Not even sure where to look for other details. Maybe Googling the identifier will help."
override def cvssScore: Option[Double] = None
}
private object UnknownVulnerabilityDescription {
def apply(name: String): VulnerabilityDescription = name match {
case cveId if name startsWith "CVE-" => new UnknownVulnerabilityDescription(name, s"https://nvd.nist.gov/vuln/detail/$cveId")
case ossIndexId if name startsWith "OSSINDEX-" => new UnknownVulnerabilityDescription(name, s"https://ossindex.net/resource/vulnerability/$ossIndexId")
case other => new TotallyUnknownVulnerabilityDescription(other)
}
}
}
class EmailExportService(from: String, nobodyInterestedContact: String, val exportType: EmailExportType.Value, odcService: OdcDbService, mailerClient: MailerClient, notificationService: VulnerabilityNotificationService, emailSendingExecutionContext: ExecutionContext, absolutizer: Absolutizer)(implicit executionContext: ExecutionContext) {
// Maybe it is not the best place for exportType, but I am not sure if we want this to be configurable. If no, then we can get rid of it. If yes, we should refactor it.
import EmailExportService.VulnerabilityDescription
private def getEmail(loginInfo: LoginInfo) = loginInfo.providerKey // TODO: get the email in a cleaner way
def recipientsForProjects(projects: Set[ReportInfo]) = for{
@@ -74,7 +35,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, val expo
}
}
def mailForVulnerabilityProjectsChange(vuln: Vulnerability, emailMessageId: EmailMessageId, diff: SetDiff[String], projects: ProjectsWithReports) = {
def mailForVulnerabilityProjectsChange(vuln: VulnerabilityOverview, emailMessageId: EmailMessageId, diff: SetDiff[String], projects: ProjectsWithReports) = {
def showProjects(s: Set[String]) = s.map(p =>
"* " + (try{
friendlyProjectNameString(projects.parseUnfriendlyName(p))
@@ -119,7 +80,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, val expo
def emailDigest(subscriber: LoginInfo, changes: Seq[Change], projects: ProjectsWithReports): Future[Email] = {
val vulnNames = changes.map(_.vulnerabilityName).toSet
for {
vulns <- Future.traverse(vulnNames.toSeq)(name => odcService.getVulnerabilityDetails(name).map(v => name -> VulnerabilityDescription(name, v))).map(_.toMap)
vulns <- Future.traverse(vulnNames.toSeq)(name => odcService.getVulnerabilityDetails(name).map(v => name -> VulnerabilityOverview(name, v))).map(_.toMap)
groups = changes.groupBy(_.direction).withDefaultValue(Seq())
} yield {
val changesMarks = Map(Direction.Added -> "❢", Direction.Removed -> "☑")
@@ -131,9 +92,9 @@ class EmailExportService(from: String, nobodyInterestedContact: String, val expo
text = "more info: "+link,
html = Html("<a href=\""+HtmlFormat.escape(link)+"\">more info</a>")
)
def vulnerabilityText(change: Change, vulnerability: VulnerabilityDescription): HtmlWithText = (
def vulnerabilityText(change: Change, vulnerability: VulnerabilityOverview): HtmlWithText = (
heading(4)(s"${changesMarks(change.direction)} ${vulnerability.name}${vulnerability.cvssScore.fold("")(sev => s" (CVSS severity: $sev)")}")
+ justHtml("<p>") + plainText(vulnerability.description) + justHtml("<br>") + justText("\n")
+ justHtml("<p>") + plainText(vulnerability.descriptionAttempt) + justHtml("<br>") + justText("\n")
+ moreInfo(absolutizer.absolutize(routes.Statistics.vulnerability(vulnerability.name, None))) + justHtml("</p>")
)
def vulnChanges(changes: Seq[Change]): HtmlWithText =