package services
import java.util.NoSuchElementException
import com.mohiva.play.silhouette.api.LoginInfo
import com.ysoft.html.HtmlWithText
import com.ysoft.html.HtmlWithText._
import com.ysoft.odc.{Absolutizer, SetDiff}
import controllers._
import models.Change.Direction
import models.{Change, EmailMessageId, VulnerabilityOverview}
import play.api.libs.mailer.{Email, MailerClient}
import play.twirl.api.{Html, HtmlFormat}
import scala.concurrent.{ExecutionContext, Future}
object EmailExportType extends Enumeration {
val Vulnerabilities = Value("vulnerabilities")
val Digest = Value("digest")
}
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.
private def getEmail(loginInfo: LoginInfo) = loginInfo.providerKey // TODO: get the email in a cleaner way
def recipientsForProjects(projects: Set[ReportInfo]) = for{
recipients <- notificationService.getRecipientsForProjects(projects)
} yield {
recipients.map(getEmail) match {
case Seq() => Seq(nobodyInterestedContact) -> false
case other => other -> true
}
}
def mailForVulnerabilityProjectsChange(vuln: VulnerabilityOverview, emailMessageId: EmailMessageId, diff: SetDiff[String], projects: ProjectsWithReports) = {
def showProjects(s: Set[String]) = s.map(p =>
"* " + (try{
friendlyProjectNameString(projects.parseUnfriendlyName(p))
}catch{ // It might fail on project that has been removed
case e: NoSuchElementException => s"unknown project $p"
})
).mkString("\n")
for{
(recipients, somebodySubscribed) <- recipientsForProjects(diff.added.map(projects.parseUnfriendlyName))
} yield Email(
subject = s"[${vuln.name}] Modified vulnerability${if(!somebodySubscribed) ", nobody is subscribed for that" else "" }",
from = from,
to = Seq(),
replyTo = emailMessageId.validIdOption,
headers = emailMessageId.validIdOption.map("References" -> _).toSeq,
bcc = recipients,
bodyText = Some(
"New projects affected by the vulnerability: \n"+showProjects(diff.added) + "\n\n" +
"Projects no longer affected by the vulnerability: \n"+showProjects(diff.removed) + "\n\n" +
s"More details: "+absolutizer.absolutize(routes.Statistics.vulnerability(vuln.name, None))
)
)
}
def sendEmail(email: Email): Future[String] = Future{
mailerClient.send(email)
}(emailSendingExecutionContext)
def mailForVulnerability(vulnerability: Vulnerability, dependencies: Set[GroupedDependency]) = for {
(recipientEmails, somebodySubscribed) <- recipientsForProjects(dependencies.flatMap(_.projects))
} yield Email(
subject = s"[${vulnerability.name}] New vulnerability${if(!somebodySubscribed) ", nobody is subscribed for that" else "" }",
from = from,
to = Seq(),
bcc = recipientEmails,
bodyText = Some(vulnerability.description + "\n\n" + s"More details: "+absolutizer.absolutize(routes.Statistics.vulnerability(vulnerability.name, None)))
)
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 -> VulnerabilityOverview(name, v))).map(_.toMap)
groups = changes.groupBy(_.direction).withDefaultValue(Seq())
} yield {
val changesMarks = Map(Direction.Added -> "❢", Direction.Removed -> "☑")
def heading(level: Int)(s: String) = HtmlWithText(
html = Html("
") + plainText(vulnerability.descriptionAttempt) + justHtml("
") + justText("\n")
+ moreInfo(absolutizer.absolutize(routes.Statistics.vulnerability(vulnerability.name, None))) + justHtml("