Initial commit

This commit is contained in:
Šesták Vít
2016-01-10 17:31:07 +01:00
commit 4b87ced31f
104 changed files with 4870 additions and 0 deletions

View File

@@ -0,0 +1,293 @@
package controllers
import java.sql.BatchUpdateException
import com.github.nscala_time.time.Imports._
import com.google.inject.Inject
import com.google.inject.name.Named
import models._
import play.api.Logger
import play.api.data.Forms._
import play.api.data._
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.http.ContentTypes
import play.api.i18n.{I18nSupport, MessagesApi}
import play.api.libs.json._
import play.api.mvc._
import play.api.routing.JavaScriptReverseRouter
import play.twirl.api.Txt
import services.{LibrariesService, LibraryTagAssignmentsService, TagsService}
import views.html.DefaultRequest
import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object ApplicationFormats{
implicit val libraryTagPairFormat = Json.format[LibraryTagPair]
implicit val libraryTagAssignmentFormat = Json.format[LibraryTagAssignment]
//implicit val libraryTypeFormat = Json.format[LibraryType]
//implicit val plainLibraryIdentifierFormat = Json.format[PlainLibraryIdentifier]
//implicit val libraryFormat = Json.format[Library]
implicit val libraryTagFormat = Json.format[LibraryTag]
}
object export {
import ApplicationFormats._
final case class AssignedTag(name: String, contextDependent: Boolean)
final case class TaggedLibrary(identifier: String, classified: Boolean, tags: Seq[AssignedTag]){
def toLibrary = Library(plainLibraryIdentifier = PlainLibraryIdentifier.fromString(identifier), classified = classified)
}
final case class Export(libraryMapping: Seq[TaggedLibrary], tags: Seq[LibraryTag])
implicit val assignedTagFormats = Json.format[AssignedTag]
implicit val taggedLibraryFormats = Json.format[TaggedLibrary]
implicit val exportFormats = Json.format[Export]
}
class Application @Inject() (
reportsParser: DependencyCheckReportsParser,
reportsProcessor: DependencyCheckReportsProcessor,
projectReportsProvider: ProjectReportsProvider,
@Named("missing-GAV-exclusions") missingGAVExclusions: MissingGavExclusions,
tagsService: TagsService,
librariesService: LibrariesService,
libraryTagAssignmentsService: LibraryTagAssignmentsService,
protected val dbConfigProvider: DatabaseConfigProvider,
val messagesApi: MessagesApi,
val env: AuthEnv
) extends AuthenticatedController with HasDatabaseConfigProvider[models.profile.type]{
import ApplicationFormats._
import dbConfig.driver.api._
import models.tables.snoozesTable
import reportsProcessor.processResults
import secureRequestConversion._
val dateFormatter = DateTimeFormat.forPattern("dd-MM-yyyy")
val emptySnoozeForm = Form(mapping(
"until" -> text.transform(LocalDate.parse(_, dateFormatter), (_: LocalDate).toString(dateFormatter)).verifying("Must be a date in the future", _ > LocalDate.now),
//"snoozed_object_identifier" -> text,
"reason" -> text(minLength = 3, maxLength = 255)
)(ObjectSnooze.apply)(ObjectSnooze.unapply))
def loadSnoozes() = {
val now = LocalDate.now
import models.jodaSupport._
for{
bareSnoozes <- db.run(snoozesTable.filter(_.until > now).result) : Future[Seq[(Int, Snooze)]]
snoozes = bareSnoozes.groupBy(_._2.snoozedObjectId).mapValues(ss => SnoozeInfo(emptySnoozeForm, ss.sortBy(_._2.until))).map(identity)
} yield snoozes.withDefaultValue(SnoozeInfo(emptySnoozeForm, Seq()))
}
def purgeCache(versions: Map[String, Int], next: String) = Action {
projectReportsProvider.purgeCache(versions)
next match {
case "index" => Redirect(routes.Application.index(versions))
case _ => Ok(Txt("CACHE PURGED"))
}
}
def index(versions: Map[String, Int]) = ReadAction.async{ implicit req =>
loadSnoozes() flatMap { snoozes =>
indexPage(versions)(snoozes, securedRequestToUserAwareRequest(req))
}
}
def indexPage(requiredVersions: Map[String, Int])(implicit snoozes: SnoozesInfo, requestHeader: DefaultRequest) = {
val (lastRefreshTimeFuture, resultsFuture) = projectReportsProvider.resultsForVersions(requiredVersions)
processResults(resultsFuture, requiredVersions).flatMap{ case (vulnerableDependencies, allWarnings, groupedDependencies) =>
Logger.debug("indexPage: Got results")
//val unclassifiedDependencies = groupedDependencies.filterNot(ds => MissingGAVExclusions.exists(_.matches(ds))).filterNot(_.identifiers.exists(_.isClassifiedInSet(classifiedSet)))
for{
knownDependencies <- librariesService.allBase
_ = Logger.debug("indexPage: #1")
includedDependencies = groupedDependencies.filterNot(missingGAVExclusions.isExcluded)
_ = Logger.debug("indexPage: #2")
unknownDependencies = includedDependencies.flatMap(_.identifiers.flatMap(_.toLibraryIdentifierOption)).toSet -- knownDependencies.map(_.plainLibraryIdentifier).toSet
_ = Logger.debug("indexPage: #3")
_ <- librariesService.insertMany(unknownDependencies.map(Library(_, classified = false)))
_ = Logger.debug("indexPage: #3")
unclassifiedDependencies <- librariesService.unclassified
_ = Logger.debug("indexPage: #4")
allTags <- tagsService.all
_ = Logger.debug("indexPage: #6")
allTagsMap = allTags.toMap
_ = Logger.debug("indexPage: #7")
tagsWithWarning = allTags.collect(Function.unlift{case (id, t: LibraryTag) => t.warningOrder.map(_ => (id, t))}).sortBy(_._2.warningOrder)
_ = Logger.debug("indexPage: #8")
librariesForTagsWithWarningUnsorted <- librariesService.librariesForTags(tagsWithWarning.map(_._1))
_ = Logger.debug("indexPage: #9")
librariesForTagsWithWarning = SortedMap(librariesForTagsWithWarningUnsorted.groupBy(_._1).toSeq.map{case (tagId, lr) => (tagId, allTagsMap(tagId)) -> lr.map(_._2) } : _*)(Ordering.by(t => (t._2.warningOrder, t._1)))
_ = Logger.debug("indexPage: #10")
relatedDependenciesTags <- librariesService.byTags(unclassifiedDependencies.map(_._1).toSet ++ librariesForTagsWithWarning.values.flatten.map(_._1).toSet)
_ = Logger.debug("indexPage: #11")
lastRefreshTime <- lastRefreshTimeFuture
} yield {
Logger.debug("indexPage: Got all ingredients")
Ok(views.html.index(
vulnerableDependencies = vulnerableDependencies,
warnings = allWarnings,
librariesForTagsWithWarning = librariesForTagsWithWarning,
unclassifiedDependencies = unclassifiedDependencies,
groupedDependencies = groupedDependencies,
dependenciesForLibraries = groupedDependencies.flatMap(group =>
group.identifiers.flatMap(_.toLibraryIdentifierOption).map(_ -> group)
).groupBy(_._1).mapValues(_.map(_._2).toSet).map(identity),
allTags = allTags,
relatedDependenciesTags = relatedDependenciesTags,
lastRefreshTime = lastRefreshTime,
versions = requiredVersions
))
}
} recover {
case e: BatchUpdateException =>
throw e.getNextException
}
}
implicit class AddAdjustToMap[K, V](m: Map[K, V]){
def adjust(k: K)(f: V => V) = m.updated(k, f(m(k)))
}
def snooze(id: String, versions: Map[String, Int]) = AdminAction.async { implicit req =>
loadSnoozes().flatMap{ loadedSnoozes =>
val snoozes = loadedSnoozes.adjust(id){_.adjustForm(_.bindFromRequest()(req))}
snoozes(id).form.fold(
f => indexPage(Map())(snoozes, securedRequestToUserAwareRequest(req)),
snooze => for {
_ <- db.run(snoozesTable.map(_.base) += snooze.toSnooze(id))
} yield Redirect(routes.Application.index(versions).withFragment(id))
)
}
}
def unsnooze(snoozeId: Int, versions: Map[String, Int]) = AdminAction.async { implicit req =>
(db.run(snoozesTable.filter(_.id === snoozeId).map(_.base).result).map(_.headOption): Future[Option[Snooze]]).flatMap {
case Some(snooze) =>
for(_ <- db.run(snoozesTable.filter(_.id === snoozeId).delete)) yield Redirect(routes.Application.index(versions).withFragment(snooze.snoozedObjectId))
case None => Future.successful(NotFound(Txt("Unknown snoozeId")))
}
}
// TODO: move import/export to a separate controller
def tagsExport = Action.async {
import export._
for{
tags <- tagsService.all.map(_.toMap)
lta <- libraryTagAssignmentsService.byLibrary
libs <- librariesService.touched(lta.keySet)
} yield {
val libraryMapping = (libs: Seq[(Int, Library)]).sortBy(_._2.plainLibraryIdentifier.toString).map { case (id, l) =>
val assignments: Seq[LibraryTagAssignment] = lta(id)
TaggedLibrary(
identifier = s"${l.plainLibraryIdentifier}",
classified = l.classified,
tags = assignments.map(a => AssignedTag(name = tags(a.tagId).name, contextDependent = a.contextDependent)).sortBy(_.name.toLowerCase)
)
}
Ok(Json.prettyPrint(Json.toJson(
Export(libraryMapping = libraryMapping, tags = tags.values.toSeq.sortBy(_.name.toLowerCase))
))).as(ContentTypes.JSON)
}
}
val tagsImportForm = Form(mapping("data" -> text)(identity)(Some(_)))
def tagsImport = AdminAction { implicit req =>
Ok(views.html.tagsImport(tagsImportForm))
}
def tagsImportAction = AdminAction.async { implicit req =>
tagsImportForm.bindFromRequest()(req).fold(
formWithErrors => ???,
data =>
export.exportFormats.reads(Json.parse(data)).fold(
invalid => Future.successful(BadRequest(Txt("ERROR: "+invalid))),
data => {
def importTags() = tagsService.insertMany(data.tags)
def getTagsByName(): Future[Map[String, Int]] = tagsService.all.map(_.groupBy(_._2.name).mapValues { case Seq((id, _)) => id }.map(identity))
def importLibraries(): Future[Unit] = Future.sequence(
data.libraryMapping.map{ taggedLibrary =>
librariesService.insert(taggedLibrary.toLibrary).flatMap{ libraryId =>
importLibraryTagAssignment(libraryId, taggedLibrary)
}
}
).map( (x: Seq[Unit]) => ()) // I don't care about the result
def importLibraryTagAssignment(libraryId: Int, taggedLibrary: export.TaggedLibrary): Future[Unit] = getTagsByName().flatMap { tagIdsByName =>
Future.sequence(taggedLibrary.tags.map{ assignedTag =>
val tagId = tagIdsByName(assignedTag.name)
libraryTagAssignmentsService.insert(LibraryTagAssignment(LibraryTagPair(libraryId = libraryId, tagId = tagId), assignedTag.contextDependent)).map(_ => ())
}).map( (x: Seq[Unit]) => ()) // I don't care about the result
}
for {
_ <- importTags()
_ <- importLibraries()
} yield Ok(Txt("OK"))
}
)
)
}
def dependencies(requiredClassification: Option[Boolean], requiredTags: Seq[Int], noTag: Boolean) = ReadAction.async { implicit request =>
val requiredTagsSet = requiredTags.toSet
for{
selectedDependencies <- db.run(librariesService.filtered(requiredClassification = requiredClassification, requiredTagsOption = if(noTag) None else Some(requiredTagsSet)).result)
dependencyTags <- librariesService.byTags(selectedDependencies.map(_._1).toSet)
allTags <- tagsService.all
}yield{
Ok(views.html.dependencies(
requiredClassification = requiredClassification,
selectedDependencies = selectedDependencies,
allTags = allTags,
dependencyTags = dependencyTags,
requiredTagSet = requiredTagsSet,
noTag = noTag,
tagsLink = (newTags: Set[Int]) => routes.Application.dependencies(requiredClassification, newTags.toSeq.sorted, noTag),
noTagLink = newNoTag => routes.Application.dependencies(requiredClassification, requiredTagsSet.toSeq.sorted, newNoTag),
classificationLink = newClassification => routes.Application.dependencies(newClassification, requiredTagsSet.toSeq.sorted, noTag)
))
}
}
def removeTag() = AdminAction.async(BodyParsers.parse.json) { request =>
request.body.validate[LibraryTagPair].fold(
err => Future.successful(BadRequest(Txt(err.toString()))),
libraryTagPair => for(_ <- libraryTagAssignmentsService.remove(libraryTagPair)) yield Ok(Txt("OK"))
)
}
def addTag() = AdminAction.async(BodyParsers.parse.json) { request =>
request.body.validate[LibraryTagAssignment].fold(
err => Future.successful(BadRequest(Txt(err.toString()))),
tagAssignment => for(_ <- libraryTagAssignmentsService.insert(tagAssignment)) yield {Ok(Txt("OK"))}
)
}
def setClassified(classified: Boolean) = AdminAction.async(BodyParsers.parse.json) {request =>
val libraryId = request.body.as[Int]
for(_ <- librariesService.setClassified(libraryId, classified)) yield Ok(Txt("OK"))
}
def javascriptRoutes = Action { implicit request =>
Ok(
JavaScriptReverseRouter("Routes")(
routes.javascript.Application.setClassified,
routes.javascript.Application.addTag
)
).as("text/javascript")
}
def testHttps(allowRedirect: Boolean) = Action { Ok(Txt(if(allowRedirect)
"""
|(function(){
| var newUrl = window.location.href.replace(/^http:/, "https:");
| if(newUrl != window.location.href){
| window.location.replace(newUrl);
| }
|})();
|""".stripMargin else "")).withHeaders("Content-type" -> "text/javascript; charset=utf-8") }
}

View File

@@ -0,0 +1,64 @@
package controllers
import javax.inject.Inject
import _root_.services.CredentialsVerificationService
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.util.Clock
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.User
import play.api.data.Form
import play.api.data.Forms._
import play.api.i18n.{Messages, MessagesApi}
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.Future
final case class LoginRequest(username: String, password: String, rememberMe: Boolean)
class AuthController @Inject() (
val messagesApi: MessagesApi,
val env: Environment[User, CookieAuthenticator],
clock: Clock,
credentialsVerificationService: CredentialsVerificationService
) extends AuthenticatedController {
val signInForm = Form(mapping(
"username" -> nonEmptyText,
"password" -> nonEmptyText,
"rememberMe" -> boolean
)(LoginRequest.apply)(LoginRequest.unapply))
def signIn = UserAwareAction { implicit request =>
request.identity match {
case Some(user) => Redirect(routes.Application.index(Map()))
case None => Ok(views.html.auth.signIn(signInForm/*, socialProviderRegistry*/))
}
}
def authenticate() = UserAwareAction.async { implicit request =>
signInForm.bindFromRequest().fold(
formWithErrors => Future.successful(BadRequest(views.html.auth.signIn(formWithErrors/*, socialProviderRegistry*/))),
loginRequest => {
credentialsVerificationService.verifyCredentials(loginRequest.username, loginRequest.password).flatMap{
case true =>
val loginInfo: LoginInfo = LoginInfo(providerID = "credentials-verification", providerKey = loginRequest.username)
val user: User = User(username = loginRequest.username)
env.authenticatorService.create(loginInfo) flatMap { authenticator =>
env.eventBus.publish(LoginEvent(user, request, implicitly[Messages]))
env.authenticatorService.init(authenticator).flatMap(cookie =>
env.authenticatorService.embed(cookie.copy(secure = request.secure), Redirect(routes.Application.index(Map())))
)
}
case false => Future.successful(Redirect(routes.AuthController.signIn()).flashing("error" -> Messages("invalid.credentials")))
}
}
)
}
def signOut = SecuredAction.async { implicit request =>
val result = Redirect(routes.Application.index(Map()))
env.eventBus.publish(LogoutEvent(request.identity, request, request2Messages))
env.authenticatorService.discard(request.authenticator, result)
}
}

View File

@@ -0,0 +1,31 @@
package controllers
import com.mohiva.play.silhouette.api.Silhouette
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.User
import play.api.mvc.{Result, RequestHeader, Results}
import views.html.DefaultRequest
import scala.concurrent.Future
import scala.language.implicitConversions
trait AuthenticatedControllerLowPriorityImplicits[T, C]{
self: AuthenticatedController =>
protected object secureRequestConversion{
implicit def securedRequestToUserAwareRequest(implicit req: SecuredRequest[_]): DefaultRequest = UserAwareRequest(Some(req.identity), authenticator = Some(req.authenticator), req.request)
}
}
abstract class AuthenticatedController extends Silhouette[User, CookieAuthenticator] with AuthenticatedControllerLowPriorityImplicits[User, CookieAuthenticator]{
override protected def onNotAuthenticated(request: RequestHeader): Option[Future[Result]] = Some(Future.successful(Redirect(routes.AuthController.signIn())))
object ReadAction extends SecuredActionBuilder with Results {
}
def AdminAction: SecuredActionBuilder = ???
}

View File

@@ -0,0 +1,143 @@
package controllers
import java.net.URLEncoder
import com.google.inject.Inject
import com.ysoft.odc._
import controllers.DependencyCheckReportsParser.Result
import models.PlainLibraryIdentifier
import play.api.Logger
import play.api.cache.CacheApi
import play.twirl.api.Html
import scala.util.{Failure, Success, Try}
sealed trait Filter{
def selector: Option[String]
def subReports(r: Result): Option[Result]
def filters: Boolean
def descriptionHtml: Html
def descriptionText: String
}
private final case class ProjectFilter(project: ReportInfo) extends Filter{
override def filters: Boolean = true
override def descriptionHtml: Html = views.html.filters.project(project)
override def descriptionText: String = s"project ${friendlyProjectName(project)}"
override def subReports(r: Result): Option[Result] = {
@inline def reportInfo = project
def f[T](m: Map[ReportInfo, T]): Map[String, T] = (
if(reportInfo.subprojectNameOption.isEmpty) m.filter(_._1.projectId == project.projectId) else m.get(reportInfo).fold(Map.empty[ReportInfo, T])(x => Map(reportInfo -> x))
).map{case (k, v) => k.fullId -> v}
val newFlatReports = f(r.flatReports)
val newFailedAnalysises = f(r.failedAnalysises)
if(newFlatReports.isEmpty && newFailedAnalysises.isEmpty) None
else Some(Result(bareFlatReports = newFlatReports, bareFailedAnalysises = newFailedAnalysises, projects = r.projects))
}
override def selector = Some(s"project:${project.fullId}")
}
private final case class TeamFilter(team: Team) extends Filter{
override def filters: Boolean = true
override def subReports(r: Result): Option[Result] = {
val reportInfoByFriendlyProjectName = r.projectsReportInfo.ungroupedReportsInfo.map(ri => friendlyProjectName(ri) -> ri).toSeq.groupBy(_._1).mapValues{
case Seq((_, ri)) => ri
case other => sys.error("some duplicate value: "+other)
}.map(identity)
val reportInfos = team.projectNames.map(reportInfoByFriendlyProjectName)
def submap[T](m: Map[String, T]) = reportInfos.toSeq.flatMap(ri => m.get(ri.fullId).map(ri.fullId -> _) ).toMap
Some(Result(
bareFlatReports = submap(r.bareFlatReports),
bareFailedAnalysises = submap(r.bareFailedAnalysises),
projects = r.projects
))
}
override def descriptionHtml: Html = views.html.filters.team(team.id)
override def descriptionText: String = s"team ${team.name}"
override def selector = Some(s"team:${team.id}")
}
object NoFilter extends Filter{
override def filters: Boolean = false
override val descriptionHtml: Html = views.html.filters.all()
override def descriptionText: String = "all projects"
override def subReports(r: Result): Option[Result] = Some(r)
override def selector: Option[String] = None
}
private final case class BadFilter(pattern: String) extends Filter{
override def filters: Boolean = true
override def subReports(r: Result): Option[Result] = None
override def descriptionHtml: Html = Html("<b>bad filter</b>")
override def descriptionText: String = "bad filter"
override def selector: Option[String] = Some(pattern)
}
object DependencyCheckReportsParser{
final case class ResultWithSelection(result: Result, projectsWithSelection: ProjectsWithSelection)
final case class Result(bareFlatReports: Map[String, Analysis], bareFailedAnalysises: Map[String, Throwable], projects: Projects){
lazy val projectsReportInfo = new ProjectsWithReports(projects, bareFlatReports.keySet ++ bareFailedAnalysises.keySet)
lazy val flatReports: Map[ReportInfo, Analysis] = bareFlatReports.map{case (k, v) => projectsReportInfo.reportIdToReportInfo(k) -> v}
lazy val failedAnalysises: Map[ReportInfo, Throwable] = bareFailedAnalysises.map{case (k, v) => projectsReportInfo.reportIdToReportInfo(k) -> v}
lazy val allDependencies = flatReports.toSeq.flatMap(r => r._2.dependencies.map(_ -> r._1))
lazy val groupedDependencies = allDependencies.groupBy(_._1.hashes).values.map(GroupedDependency(_)).toSeq
lazy val groupedDependenciesByPlainLibraryIdentifier: Map[PlainLibraryIdentifier, Set[GroupedDependency]] =
groupedDependencies.toSet.flatMap((grDep: GroupedDependency) => grDep.plainLibraryIdentifiers.map(_ -> grDep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
lazy val vulnerableDependencies = groupedDependencies.filter(_.vulnerabilities.nonEmpty)
private val ProjectSelectorPattern = """^project:(.*)$""".r
private val TeamSelectorPattern = """^team:(.*)$""".r
private def parseFilter(filter: String): Filter = filter match {
case ProjectSelectorPattern(project) => ProjectFilter(projectsReportInfo.reportIdToReportInfo(project))
case TeamSelectorPattern(team) => TeamFilter(projects.teamById(team))
case other => BadFilter(other)
}
def selection(selectorOption: Option[String]): Option[ResultWithSelection] = {
val filter = selectorOption.map(parseFilter).getOrElse(NoFilter)
filter.subReports(this).map{ result =>
ResultWithSelection(
result = result,
projectsWithSelection = ProjectsWithSelection(filter = filter, projectsWithReports = projectsReportInfo, teams = projects.teamSet)
)
}
}
}
}
final class DependencyCheckReportsParser @Inject() (cache: CacheApi, projects: Projects) {
def parseReports(successfulResults: Map[String, (Build, ArtifactItem, ArtifactFile)]) = {
val rid = math.random.toString // for logging
@volatile var parseFailedForSomeAnalysis = false
val deepReportsTriesIterable: Iterable[Map[String, Try[Analysis]]] = for((k, (build, data, log)) <- successfulResults) yield {
Logger.debug(data.flatFilesWithPrefix(s"$k/").keySet.toSeq.sorted.toString)
val flat = data.flatFilesWithPrefix(s"$k/")
(for((k, v) <- flat.par) yield {
val analysisKey = URLEncoder.encode(s"analysis/parsedXml/${build.buildResultKey}/${k}", "utf-8")
Logger.debug(s"[$rid] analysisKey: $analysisKey")
val analysisTry = cache.getOrElse(analysisKey)(Try{OdcParser.parseXmlReport(v)})
analysisTry match{
case Success(e) => // nothing
case Failure(e) =>
if(!parseFailedForSomeAnalysis){
Logger.error(s"[$rid] Cannot parse $k: ${new String(v, "utf-8")}", e)
parseFailedForSomeAnalysis = true
}
}
k -> analysisTry
}).seq
}
val deepReportsAndFailuresIterable = deepReportsTriesIterable.map { reports =>
val (successfulReportsTries, failedReportsTries) = reports.partition(_._2.isSuccess)
val successfulReports = successfulReportsTries.mapValues(_.asInstanceOf[Success[Analysis]].value).map(identity)
val failedReports = failedReportsTries.mapValues(_.asInstanceOf[Failure[Analysis]].exception).map(identity)
(successfulReports, failedReports)
}
val deepSuccessfulReports = deepReportsAndFailuresIterable.map(_._1).toSeq
val failedAnalysises = deepReportsAndFailuresIterable.map(_._2).toSeq.flatten.toMap
val flatReports = deepSuccessfulReports.flatten.toMap
Logger.debug(s"[$rid] parse finished")
Result(flatReports, failedAnalysises, projects)
}
}

View File

@@ -0,0 +1,106 @@
package controllers
import com.github.nscala_time.time.Imports._
import com.google.inject.Inject
import com.google.inject.name.Named
import com.ysoft.odc.Checks._
import com.ysoft.odc._
import org.joda.time.DateTimeConstants
import play.api.Logger
import play.api.i18n.{I18nSupport, MessagesApi}
import play.api.mvc.RequestHeader
import play.twirl.api.Html
import views.html.DefaultRequest
import scala.concurrent.{ExecutionContext, Future}
final case class MissingGavExclusions(exclusionsSet: Set[Exclusion]){
def isExcluded(groupedDependency: GroupedDependency) = exclusionsSet.exists(_.matches(groupedDependency))
}
final class DependencyCheckReportsProcessor @Inject() (
@Named("bamboo-server-url") val server: String,
dependencyCheckReportsParser: DependencyCheckReportsParser,
@Named("missing-GAV-exclusions") missingGAVExclusions: MissingGavExclusions,
val messagesApi: MessagesApi
) extends I18nSupport {
private def parseDateTime(dt: String): DateTime = {
if(dt.forall(_.isDigit)){
new DateTime(dt.toLong) // TODO: timezone (I don't care much, though)
}else{
val formatter = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss") // TODO: timezone (I don't care much, though)
formatter.parseDateTime(dt)
}
}
@deprecated("use HTML output instead", "SNAPSHOT") private val showDependencies: (Seq[GroupedDependency]) => Seq[String] = {
_.map { s =>
s.dependencies.map { case (dep, projects) => s"${dep.fileName} @ ${projects.toSeq.sorted.map(friendlyProjectName).mkString(", ")}" }.mkString(", ") + " " + s.hashes
}
}
def processResults(
resultsFuture: Future[(Map[String, (Build, ArtifactItem, ArtifactFile)], Map[String, Throwable])],
requiredVersions: Map[String, Int]
)(implicit requestHeader: DefaultRequest, snoozesInfo: SnoozesInfo, executionContext: ExecutionContext) = try{
for((successfulResults, failedResults) <- resultsFuture) yield{
val reportResult = dependencyCheckReportsParser.parseReports(successfulResults)
import reportResult.{allDependencies, failedAnalysises, flatReports, groupedDependencies, vulnerableDependencies}
val now = DateTime.now
val oldReportThreshold = now - 1.day
val cveTimestampThreshold = now - (if(now.dayOfWeek().get == DateTimeConstants.MONDAY) 4.days else 2.days )
val ScanChecks: Seq[Map[ReportInfo, Analysis] => Option[Warning]] = Seq(
differentValues("scan infos", "scan-info", WarningSeverity.Warning)(_.groupBy(_._2.scanInfo).mapValues(_.keys.toIndexedSeq.sorted)),
badValues("old-reports", "old reports", WarningSeverity.Warning)((_, a) => if(a.reportDate < oldReportThreshold) Some(Html(a.reportDate.toString)) else None),
badValues("bad-cve-data", "old or no CVE data", WarningSeverity.Warning){(_, analysis) =>
(analysis.scanInfo.xml \\ "timestamp").map(_.text).filterNot(_ == "").map(parseDateTime) match {
case Seq() => Some(Html("no data"))
case timestamps =>
val newestTimestamp = timestamps.max
val oldestTimestamp = timestamps.min
if(newestTimestamp < cveTimestampThreshold) Some(Html(newestTimestamp.toString))
else None
}
}
)
val GroupedDependenciesChecks = Seq[Seq[GroupedDependency] => Option[Warning]](
badGroupedDependencies("unidentified-dependencies", "unidentified dependencies", WarningSeverity.Info)(_.filter(_.dependencies.exists(_._1.identifiers.isEmpty)))(show = showDependencies, exclusions = missingGAVExclusions.exclusionsSet),
badGroupedDependencies("different-identifier-sets", "different identifier sets", WarningSeverity.Info)(_.filter(_.dependencies.groupBy(_._1.identifiers).size > 1).toIndexedSeq)(),
badGroupedDependencies("different-evidence", "different evidence", WarningSeverity.Info)(_.filter(_.dependencies.groupBy(_._1.evidenceCollected).size > 1).toIndexedSeq)(show = x => Some(views.html.warnings.groupedDependencies(x))),
badGroupedDependencies("missing-gav", "missing GAV", WarningSeverity.Info)(_.filter(_.identifiers.filter(_.identifierType == "maven").isEmpty))(show = showDependencies, exclusions = missingGAVExclusions.exclusionsSet)
)
val unknownIdentifierTypes = allDependencies.flatMap(_._1.identifiers.map(_.identifierType)).toSet -- Set("maven", "cpe")
val failedReports = successfulResults.filter(x => x._2._1.state != "Successful" || x._2._1.buildState != "Successful")
val extraWarnings = Seq[Option[Warning]](
if(failedReports.size > 0) Some(IdentifiedWarning("failed-reports", views.html.warnings.failedReports(failedReports.values.map{case (b, _ ,_) => b}.toSet, server), WarningSeverity.Error)) else None,
if(unknownIdentifierTypes.size > 0) Some(IdentifiedWarning("unknown-identifier-types", views.html.warnings.unknownIdentifierType(unknownIdentifierTypes), WarningSeverity.Info)) else None,
{
val emptyResults = successfulResults.filter{case (k, (_, dir, _)) => dir.flatFiles.size < 1}
if(emptyResults.nonEmpty) Some(IdentifiedWarning("empty-results", views.html.warnings.emptyResults(emptyResults.values.map{case (build, _, _) => build}.toSeq, server), WarningSeverity.Warning)) else None
},
{
val resultsWithErrorMessages = successfulResults.filter{case (k, (_, _, log)) => log.dataString.lines.exists(l => (l.toLowerCase startsWith "error") || (l.toLowerCase contains "[error]"))}
if(resultsWithErrorMessages.nonEmpty) Some(IdentifiedWarning("results-with-error-messages", views.html.warnings.resultsWithErrorMessages(resultsWithErrorMessages.values.map{case (build, _, _) => build}.toSeq, server), WarningSeverity.Error)) else None
},
if(failedResults.isEmpty) None else Some(IdentifiedWarning("failed-results", views.html.warnings.failedResults(failedResults), WarningSeverity.Error)),
if(requiredVersions.isEmpty) None else Some(IdentifiedWarning("required-versions", views.html.warnings.textWarning("You have manually requested results for some older version."), WarningSeverity.Warning)),
if(failedAnalysises.isEmpty) None else Some(IdentifiedWarning("failed-analysises", views.html.warnings.textWarning(s"Some reports failed to parse: ${failedAnalysises.keySet}"), WarningSeverity.Error))
).flatten
val scanWarnings = ScanChecks.flatMap(_(flatReports))
val groupedDependenciesWarnings = GroupedDependenciesChecks.flatMap(_(groupedDependencies))
val allWarnings = scanWarnings ++ groupedDependenciesWarnings ++ extraWarnings
// TODO: log analysis
// TODO: related dependencies
(vulnerableDependencies, allWarnings, groupedDependencies)
}
}finally{
Logger.debug("Reports processed")
}
}

View File

@@ -0,0 +1,40 @@
package controllers
import com.github.nscala_time.time.Imports._
import com.google.inject.Inject
import com.ysoft.odc.Downloader
import play.api.cache.CacheApi
import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag
import scala.util.Success
class ProjectReportsProvider @Inject() (
bambooDownloader: Downloader,
cache: CacheApi,
projects: Projects
)(implicit executionContext: ExecutionContext){
private def bambooCacheKey(versions: Map[String, Int]) = "bamboo/results/" + versions.toSeq.sorted.map{case (k, v) => k.getBytes("utf-8").mkString("-") + ":" + v}.mkString("|")
def purgeCache(versions: Map[String, Int]) = cache.remove(bambooCacheKey(versions))
private def getOrElseFuture[T: ClassTag]
(name: String, expiration: scala.concurrent.duration.Duration = scala.concurrent.duration.Duration.Inf)
(f: => Future[T])
(implicit executionContext: ExecutionContext): Future[T] =
{
cache.get[T](name).map(Future.successful).getOrElse(
f.andThen{
case Success(value) =>cache.set(name, value, expiration)
}
)
}
def resultsForVersions(versions: Map[String, Int]) = {
def get = {val time = DateTime.now; bambooDownloader.downloadProjectReports(projects.projectSet, versions).map(time -> _)}
val allFuture = getOrElseFuture(bambooCacheKey(versions)){println("CACHE MISS"); get}
(allFuture.map(_._1), allFuture.map(_._2))
}
}

View File

@@ -0,0 +1,52 @@
package controllers
import javax.inject.Inject
import play.api.Configuration
class Projects @Inject() (configuration: Configuration) {
import scala.collection.JavaConversions._
val projectMap = {
val projectsConfig = configuration.getObject("yssdc.projects").getOrElse(sys.error("yssdc.projects is not set")).toConfig
projectsConfig.entrySet().map( k => k.getKey -> projectsConfig.getString(k.getKey)).toMap
}
val projectSet = projectMap.keySet
val teamIdSet = configuration.getStringSeq("yssdc.teams").getOrElse(sys.error("yssdc.teams is not set")).map(TeamId).toSet
private val teamsByIds = teamIdSet.map(t => t.id -> t).toMap
val teamLeaders = {
import scala.collection.JavaConversions._
configuration.getObject("yssdc.teamLeaders").getOrElse(sys.error("yssdc.teamLeaders is not set")).map{case(k, v) =>
TeamId(k) -> v.unwrapped().asInstanceOf[String]
}
}
{
val extraTeams = teamLeaders.keySet -- teamIdSet
if(extraTeams.nonEmpty){
sys.error(s"Some unexpected teams: $extraTeams")
}
}
def existingTeamId(s: String): TeamId = teamsByIds(s)
val projectToTeams = configuration.getObject("yssdc.projectsToTeams").get.mapValues{_.unwrapped().asInstanceOf[java.util.List[String]].map(c =>
existingTeamId(c)
).toSet}.map(identity)
val projectAndTeams = projectToTeams.toSeq.flatMap{case (project, teams) => teams.map(team => (project, team))}
val teamsToProjects = projectAndTeams.groupBy(_._2).mapValues(_.map(_._1).toSet).map(identity)
val teamsById: Map[String, Team] = for{
(team, projectNames) <- teamsToProjects
} yield team.id -> Team(
id = team.id,
name = team.name,
leader = teamLeaders(team),
projectNames = projectNames
)
def teamById(id: String) = teamsById(id)
def teamSet = teamsById.values.toSet
}

View File

@@ -0,0 +1,58 @@
package controllers
final case class ReportInfo(
projectId: String,
projectName: String,
fullId: String,
subprojectNameOption: Option[String]
) extends Ordered[ReportInfo] {
import scala.math.Ordered.orderingToOrdered
override def compare(that: ReportInfo): Int = ((projectName, subprojectNameOption, fullId)) compare ((that.projectName, that.subprojectNameOption, that.fullId))
// It seems to be a good idea to have a custom equals and hashCode for performance reasons
override def equals(other: Any): Boolean = other match {
case other: ReportInfo => fullId == other.fullId
case _ => false
}
override def hashCode(): Int = 517+fullId.hashCode
}
object ProjectsWithReports{
private val RestMessBeginRegexp = """^/Report results-XML/""".r
private val RestMessEndRegexp = """/(target/)?dependency-check-report\.xml$""".r
}
class ProjectsWithReports (val projects: Projects, val reports: Set[String]) {
import ProjectsWithReports._
val reportIdToReportInfo = {
val reportsMap = reports.map{ unfriendlyName =>
val (baseName, theRest) = unfriendlyName.span(_ != '/')
val removeLeadingMess = RestMessBeginRegexp.replaceAllIn(_: String, "")
val removeTrailingMess = RestMessEndRegexp.replaceAllIn(_: String, "")
val removeMess = removeLeadingMess andThen removeTrailingMess
val subProjectOption = Some(removeMess(theRest)).filter(_ != "")
subProjectOption.fold(baseName)(baseName+"/"+_)
unfriendlyName -> ReportInfo(
projectId = baseName,
fullId = unfriendlyName,
projectName = projects.projectMap(baseName),
subprojectNameOption = subProjectOption
)
}.toMap
reportsMap ++ reportsMap.values.map(r => r.projectId -> ReportInfo(projectId = r.projectId, fullId = r.projectId, subprojectNameOption = None, projectName = r.projectName))
}
val ungroupedReportsInfo = reportIdToReportInfo.values.toSet
}

View File

@@ -0,0 +1,14 @@
package controllers
final case class TeamId(id: String) extends AnyVal {
def name = id
}
final case class Team(id: String, name: String, leader: String, projectNames: Set[String])
// TODO: rename to something more sane. It is maybe rather FilteringData now.
final case class ProjectsWithSelection(filter: Filter, projectsWithReports: ProjectsWithReports, teams: Set[Team]) {
def isProjectSpecified: Boolean = filter.filters
def selectorString = filter.selector
def projectNameText: String = filter.descriptionText
}

View File

@@ -0,0 +1,226 @@
package controllers
import com.github.nscala_time.time.Imports._
import com.google.inject.Inject
import com.google.inject.name.Named
import com.ysoft.odc.{ArtifactFile, ArtifactItem}
import models.{Library, LibraryTag}
import org.joda.time.DateTime
import play.api.i18n.MessagesApi
import play.twirl.api.Txt
import services.{LibrariesService, LibraryTagAssignmentsService, OdcService, TagsService}
import views.html.DefaultRequest
import scala.concurrent.{ExecutionContext, Future}
object Statistics{
case class LibDepStatistics(libraries: Set[(Int, Library)], dependencies: Set[GroupedDependency]){
def vulnerableRatio = vulnerableDependencies.size.toDouble / dependencies.size.toDouble
lazy val vulnerabilities: Set[Vulnerability] = dependencies.flatMap(_.vulnerabilities)
lazy val vulnerabilitiesToDependencies: Map[Vulnerability, Set[GroupedDependency]] = vulnerableDependencies.flatMap(dep =>
dep.vulnerabilities.map(vuln => (vuln, dep))
).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(_ -> dep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
lazy val vulnerableDependencies = dependencies.filter(_.isVulnerable)
lazy val (dependenciesWithCpe, dependenciesWithoutCpe) = dependencies.partition(_.hasCpe)
lazy val cpeRatio = dependenciesWithCpe.size.toDouble / dependencies.size.toDouble
lazy val weaknesses = vulnerabilities.flatMap(_.cweOption)
lazy val weaknessesFrequency = computeWeaknessesFrequency(vulnerabilities)
}
case class TagStatistics(tagRecord: (Int, LibraryTag), stats: LibDepStatistics){
def tag: LibraryTag = tagRecord._2
def tagId: Int = tagRecord._1
}
def computeWeaknessesFrequency(vulnerabilities: Set[Vulnerability]) = vulnerabilities.toSeq.map(_.cweOption).groupBy(identity).mapValues(_.size).map(identity).withDefaultValue(0)
}
import controllers.Statistics._
class Statistics @Inject() (
reportsParser: DependencyCheckReportsParser,
reportsProcessor: DependencyCheckReportsProcessor,
projectReportsProvider: ProjectReportsProvider,
dependencyCheckReportsParser: DependencyCheckReportsParser,
librariesService: LibrariesService,
tagsService: TagsService,
odcService: OdcService,
libraryTagAssignmentsService: LibraryTagAssignmentsService,
@Named("missing-GAV-exclusions") missingGAVExclusions: MissingGavExclusions,
projects: Projects,
val env: AuthEnv
)(implicit val messagesApi: MessagesApi, executionContext: ExecutionContext) extends AuthenticatedController {
private val versions = Map[String, Int]()
private def notFound()(implicit req: DefaultRequest) = {
NotFound(views.html.defaultpages.notFound("GET", req.uri))
}
import secureRequestConversion._
private def select(successfulResults: Map[String, (Build, ArtifactItem, ArtifactFile)], selectorOption: Option[String]) = dependencyCheckReportsParser.parseReports(successfulResults).selection(selectorOption)
def searchVulnerableSoftware(versionlessCpes: Seq[String], versionOption: Option[String]) = ReadAction.async{ implicit req =>
if(versionlessCpes.isEmpty){
Future.successful(notFound())
}else{
val now = DateTime.now()
val oldDataThreshold = 2.days
val lastDbUpdateFuture = odcService.loadLastDbUpdate()
val isOldFuture = lastDbUpdateFuture.map{ lastUpdate => now - oldDataThreshold > lastUpdate}
versionOption match {
case Some(version) =>
for {
res1 <- Future.traverse(versionlessCpes) { versionlessCpe => odcService.findRelevantCpes(versionlessCpe, version) }
vulnIds = res1.flatten.map(_.vulnerabilityId).toSet
vulns <- Future.traverse(vulnIds)(id => odcService.getVulnerabilityDetails(id).map(_.get))
isOld <- isOldFuture
} yield Ok(views.html.statistics.vulnerabilitiesForLibrary(
vulnsAndVersionOption = Some((vulns, version)),
cpes = versionlessCpes,
isDbOld = isOld
))
case None =>
for(isOld <- isOldFuture) yield Ok(views.html.statistics.vulnerabilitiesForLibrary(
vulnsAndVersionOption = None,
cpes = versionlessCpes,
isDbOld = isOld
))
}
}
}
def basic(projectOption: Option[String]) = ReadAction.async{ implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, projectOption).fold(Future.successful(notFound())){ selection =>
val tagsFuture = tagsService.all
val parsedReports = selection.result
for{
tagStatistics <- statisticsForTags(parsedReports, tagsFuture)
} yield Ok(views.html.statistics.basic(
tagStatistics = tagStatistics,
projectsWithSelection = selection.projectsWithSelection,
parsedReports = parsedReports
))
}
}
}
def statisticsForTags(parsedReports: DependencyCheckReportsParser.Result, tagsFuture: Future[Seq[(Int, LibraryTag)]]): Future[Seq[Statistics.TagStatistics]] = {
val librariesFuture = librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
val libraryTagAssignmentsFuture = librariesFuture.flatMap{libraries => libraryTagAssignmentsService.forLibraries(libraries.values.map(_._1).toSet)}
val tagsToLibrariesFuture = libraryTagAssignmentsService.tagsToLibraries(libraryTagAssignmentsFuture)
val librariesToDependencies = parsedReports.groupedDependenciesByPlainLibraryIdentifier
for{
librariesById <- librariesFuture.map(_.values.toMap)
tagsToLibraries <- tagsToLibrariesFuture
tags <- tagsFuture
} yield tags.flatMap{case tagRecord @ (tagId, tag) =>
val libraryAssignments = tagsToLibraries(tagId)
val tagLibraries = libraryAssignments.map(a => a.libraryId -> librariesById(a.libraryId))
val tagDependencies: Set[GroupedDependency] = tagLibraries.flatMap{case (_, lib) => librariesToDependencies(lib.plainLibraryIdentifier)}
// TODO: vulnerabilities in the past
if(tagLibraries.isEmpty) None
else Some(TagStatistics(tagRecord = tagRecord, stats = LibDepStatistics(libraries = tagLibraries, dependencies = tagDependencies)))
}
}
def vulnerabilities(projectOption: Option[String], tagIdOption: Option[Int]) = ReadAction.async {implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, projectOption).fold(Future.successful(notFound())){ selection =>
val parsedReports = selection.result
for{
libraries <- librariesService.byPlainLibraryIdentifiers(parsedReports.allDependencies.flatMap(_._1.plainLibraryIdentifiers).toSet)
tagOption <- tagIdOption.fold[Future[Option[(Int, LibraryTag)]]](Future.successful(None))(tagId => tagsService.getById(tagId).map(Some(_)))
statistics <- tagOption.fold(Future.successful(LibDepStatistics(dependencies = parsedReports.groupedDependencies.toSet, libraries = libraries.values.toSet))){ tag =>
statisticsForTags(parsedReports, Future.successful(Seq(tag))).map{
case Seq(TagStatistics(_, stats)) => stats // statisticsForTags is designed for multiple tags, but we have just one…
case Seq() => LibDepStatistics(libraries = Set(), dependencies = Set()) // We don't want to crash when no dependencies are there…
}
}
} yield Ok(views.html.statistics.vulnerabilities(
projectsWithSelection = selection.projectsWithSelection,
tagOption = tagOption,
statistics = statistics
))
}
}
}
def vulnerability(name: String, projectOption: Option[String]) = ReadAction.async { implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, projectOption).fold(Future.successful(notFound())){ selection =>
val relevantReports = selection.result
val vulns = relevantReports.vulnerableDependencies.flatMap(dep => dep.vulnerabilities.map(vuln => (vuln, dep))).groupBy(_._1.name).mapValues{case vulnsWithDeps =>
val (vulnSeq, depSeq) = vulnsWithDeps.unzip
val Seq(vuln) = vulnSeq.toSet.toSeq // Will fail when there are more different descriptions for one vulnerability…
vuln -> depSeq.toSet
}// .map(identity) // The .map(identity) materializes lazily mapped Map (because .mapValues is lazy). I am, however, unsure if this is a good idea. Probably not.
vulns.get(name).fold(Future.successful(Ok(views.html.statistics.vulnerabilityNotFound(
name = name,
projectsWithSelection = selection.projectsWithSelection
)))){ case (vuln, vulnerableDependencies) =>
for(
plainLibs <- librariesService.byPlainLibraryIdentifiers(vulnerableDependencies.flatMap(_.plainLibraryIdentifiers)).map(_.keySet)
) yield Ok(views.html.statistics.vulnerability(
vulnerability = vuln,
affectedProjects = vulnerableDependencies.flatMap(dep => dep.projects.map(proj => (proj, dep))).groupBy(_._1).mapValues(_.map(_._2)),
vulnerableDependencies = vulnerableDependencies,
affectedLibraries = plainLibs,
projectsWithSelection = selection.projectsWithSelection
))
}
}
}
}
def vulnerableLibraries(project: Option[String]) = ReadAction.async { implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, project).fold(Future.successful(notFound())){selection =>
val reports = selection.result
Future.successful(Ok(views.html.statistics.vulnerableLibraries(
projectsWithSelection = selection.projectsWithSelection,
vulnerableDependencies = reports.vulnerableDependencies,
allDependenciesCount = reports.groupedDependencies.size
)))
}
}
}
def allLibraries(project: Option[String]) = ReadAction.async { implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, project).fold(Future.successful(notFound())){selection =>
Future.successful(Ok(views.html.statistics.allLibraries(
projectsWithSelection = selection.projectsWithSelection,
allDependencies = selection.result.groupedDependencies
)))
}
}
}
def allGavs(project: Option[String]) = ReadAction.async { implicit req =>
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
resultsFuture flatMap { case (successfulResults, failedResults) =>
select(successfulResults, project).fold(Future.successful(notFound())){selection =>
Future.successful(Ok(Txt(
selection.result.groupedDependencies.flatMap(_.mavenIdentifiers).toSet.toIndexedSeq.sortBy((id: Identifier) => (id.identifierType, id.name)).map(id => id.name.split(':') match {
case Array(g, a, v) =>
s""""${id.identifierType}", "$g", "$a", "$v", "${id.url}" """
}).mkString("\n")
)))
}
}
}
}

View File

@@ -0,0 +1,37 @@
import com.mohiva.play.silhouette.api.Environment
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.{User, SnoozeInfo}
/**
* Created by user on 7/15/15.
*/
package object controllers {
// Imports for all templates. Those could be added directly to the template files, but IntelliJ IDEA does not like it.
type Dependency = com.ysoft.odc.Dependency
type Build = com.ysoft.odc.Build
type GroupedDependency = com.ysoft.odc.GroupedDependency
type Vulnerability = com.ysoft.odc.Vulnerability
type Identifier = com.ysoft.odc.Identifier
type DateTime = org.joda.time.DateTime
type SnoozesInfo = Map[String, SnoozeInfo]
type AuthEnv = Environment[User, CookieAuthenticator]
val NormalUrlPattern = """^(http(s?)|ftp(s?))://.*""".r
val TooGenericDomains = Set("sourceforge.net", "github.com", "github.io")
/* def friendlyProjectName(unfriendlyName: String) = {
val (baseName, theRest) = unfriendlyName.span(_ != '/')
//theRest.drop(1)
val removeLeadingMess = RestMessBeginRegexp.replaceAllIn(_: String, "")
val removeTrailingMess = RestMessEndRegexp.replaceAllIn(_: String, "")
val removeMess = removeLeadingMess andThen removeTrailingMess
val subProjectOption = Some(removeMess(theRest)).filter(_ != "")
subProjectOption.fold(baseName)(baseName+"/"+_)
}*/
def friendlyProjectName(reportInfo: ReportInfo) = reportInfo.subprojectNameOption.fold(reportInfo.projectName)(reportInfo.projectName+": "+_)
}

View File

@@ -0,0 +1,21 @@
package controllers
import controllers.WarningSeverity.WarningSeverity
import play.twirl.api.Html
object WarningSeverity extends Enumeration {
type WarningSeverity = Value
// Order is important
val Info = Value("info")
val Warning = Value("warning")
val Error = Value("error")
}
sealed abstract class Warning {
def html: Html
def id: String
def allowSnoozes = true
def severity: WarningSeverity
}
final case class IdentifiedWarning(id: String, html: Html, severity: WarningSeverity) extends Warning