Failsafe behavior for e-mail notifications.

If the vulnerability is not found, it now does its best for providing relevant information. Even if no additional information (other than vulnerability identifier) can be provided, it does not cause an exception when sending e-mail. Which is the main point of this improvement.
This commit is contained in:
Šesták Vít
2017-10-11 15:48:20 +02:00
parent 8688ffd730
commit cdb31dcc4e

View File

@@ -17,11 +17,51 @@ import scala.concurrent.{ExecutionContext, Future}
object EmailExportType extends Enumeration { object EmailExportType extends Enumeration {
val Vulnerabilities = Value("vulnerabilities") val Vulnerabilities = Value("vulnerabilities")
val Digest = Value("digest") 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) { 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. // 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 private def getEmail(loginInfo: LoginInfo) = loginInfo.providerKey // TODO: get the email in a cleaner way
@@ -79,7 +119,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, val expo
def emailDigest(subscriber: LoginInfo, changes: Seq[Change], projects: ProjectsWithReports): Future[Email] = { def emailDigest(subscriber: LoginInfo, changes: Seq[Change], projects: ProjectsWithReports): Future[Email] = {
val vulnNames = changes.map(_.vulnerabilityName).toSet val vulnNames = changes.map(_.vulnerabilityName).toSet
for { for {
vulns <- Future.traverse(vulnNames.toSeq)(name => odcService.getVulnerabilityDetails(name).map(v => name -> v.getOrElse(throw new NoSuchElementException(s"Vulnerability details for '$name' not found.")))).map(_.toMap) vulns <- Future.traverse(vulnNames.toSeq)(name => odcService.getVulnerabilityDetails(name).map(v => name -> VulnerabilityDescription(name, v))).map(_.toMap)
groups = changes.groupBy(_.direction).withDefaultValue(Seq()) groups = changes.groupBy(_.direction).withDefaultValue(Seq())
} yield { } yield {
val changesMarks = Map(Direction.Added -> "❢", Direction.Removed -> "☑") val changesMarks = Map(Direction.Added -> "❢", Direction.Removed -> "☑")
@@ -91,7 +131,7 @@ class EmailExportService(from: String, nobodyInterestedContact: String, val expo
text = "more info: "+link, text = "more info: "+link,
html = Html("<a href=\""+HtmlFormat.escape(link)+"\">more info</a>") html = Html("<a href=\""+HtmlFormat.escape(link)+"\">more info</a>")
) )
def vulnerabilityText(change: Change, vulnerability: Vulnerability): HtmlWithText = ( def vulnerabilityText(change: Change, vulnerability: VulnerabilityDescription): HtmlWithText = (
heading(4)(s"${changesMarks(change.direction)} ${vulnerability.name}${vulnerability.cvssScore.fold("")(sev => s" (CVSS severity: $sev)")}") 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.description) + justHtml("<br>") + justText("\n")
+ moreInfo(absolutizer.absolutize(routes.Statistics.vulnerability(vulnerability.name, None))) + justHtml("</p>") + moreInfo(absolutizer.absolutize(routes.Statistics.vulnerability(vulnerability.name, None))) + justHtml("</p>")