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,12 @@
@import helper._
@(loginForm: Form[LoginRequest]/*, socialProviderRegistry: SocialProviderRegistry*/)(implicit requestHeader: DefaultRequest, messages: Messages)
@main("Log in"){
@form(routes.AuthController.authenticate()){
@CSRF.formField
@inputText(loginForm("username"))
@inputPassword(loginForm("password"))
@* checkbox(loginForm("rememberMe")) *@
<button type="submit">Log me in!</button>
}
}

View File

@@ -0,0 +1,18 @@
@(list: Traversable[_], name: String, id: String, collapse: Boolean = false, allowSnoozes: Boolean = true, versions: Map[String, Int])(content: => Html)(implicit rh: DefaultRequest, snoozes: SnoozesInfo, messages: Messages)
@if(list.nonEmpty){
@defining(snoozes(id)){ case si =>
@if(allowSnoozes) {
@snoozeButton(id, si, collapse)
}
<h2 id="@id" @if(si.isSnoozed){class="text-muted"} data-toggle="collapse" data-target="#@id-div, #@id-snooze-button">@if(si.isSnoozed){&#128564;} @name (@{list.size})</h2>
<div id="@id-div" class="collapse@if(!si.shouldCollapse(collapse)){ in}">
@if(allowSnoozes) {
@snoozeForm(id, si, versions)
}
@content
@if(allowSnoozes) {
@snoozesList(id, si, versions)
}
</div>
}
}

View File

@@ -0,0 +1,40 @@
@(
selectedDependencies: Seq[(Int, Library)],
allTags: Seq[(Int, LibraryTag)],
dependencyTags: Map[Int, Set[LibraryTagAssignment]],
requiredClassification: Option[Boolean],
requiredTagSet: Set[Int],
noTag: Boolean,
tagsLink: Set[Int] => Call,
classificationLink: Option[Boolean] => Call,
noTagLink: Boolean => Call
)(implicit header: DefaultRequest)
@main(s"${requiredClassification match{case Some(true) => "Classified" case Some(false) => "Unclassified" case None => "All"}} dependencies (${selectedDependencies.size})") {
<div>
<div class="btn-group">
@for((newClassification, name) <- Seq(None -> "All", Some(true) -> "Classified", Some(false) -> "Unclassified"); isCurrent = newClassification == requiredClassification){
<a class="btn @if(isCurrent){active} btn-default" href="@classificationLink(newClassification)">@name</a>
}
</div>
</div>
<a href="@noTagLink(!noTag)" class="btn btn-primary @if(!noTag){active}">Required tags:</a>
@if(!noTag) {
@for((tagId, tag) <- allTags.sortBy(_._2.name); enabled = requiredTagSet contains tagId) {
<a
href="@tagsLink(if(enabled) requiredTagSet - tagId else requiredTagSet + tagId)"
class="btn btn-default @if(requiredTagSet contains tagId) {active btn-success}"
title="@tag.note"
>@tag.name</a>
}
}
<hr>
@dependencyClassification(
prefix = "dependency",
dependencies = selectedDependencies,
allTags = allTags,
dependenciesTags = dependencyTags,
details = (_, _) => {
Html("")
}
)
}

View File

@@ -0,0 +1,40 @@
@(
prefix: String,
dependencies: Seq[(Int, Library)],
allTags: Seq[(Int, LibraryTag)],
dependenciesTags: Map[Int, Set[LibraryTagAssignment]],
details: (Int, PlainLibraryIdentifier) => Html
)
<ul class="table">
@for((libraryId, Library(lib, classified)) <- dependencies){
<li class="versionless-dependency">
<a href="#@prefix-@libraryId" data-toggle="collapse">@lib.libraryType: @lib.libraryIdentifier</a>
<span class="related-links">
@if(lib.libraryType == LibraryType.Maven){
<a class="text-muted" href="http://mvnrepository.com/artifact/@lib.libraryIdentifier.replaceFirst(":", "/")">mvnrepository.com»</a>
<a class="text-muted" href="https://libraries.io/maven/@lib.libraryIdentifier">libraries.io»</a>
@defining(lib.libraryIdentifier.takeWhile(_ != ':').split('.')){ reverseDomain =>
@if(! reverseDomain.startsWith(Seq("javax")) ){
@for(i <- reverseDomain.length to 2 by -1; guessedDomain = reverseDomain.take(i).reverse.mkString("."); if !(TooGenericDomains contains guessedDomain)){
<a class="text-muted" href="http://@guessedDomain">@guessedDomain»</a>
}
}
}
}
<a class="text-muted" href="https://www.google.com/search?q=@helper.urlEncode(lib.libraryIdentifier)&ie=utf-8&oe=utf-8">Google»</a>
</span>
<div id="@prefix-@libraryId" class="collapse">
@details(libraryId, lib)
@defining(dependenciesTags.getOrElse(libraryId, Set.empty)) { libraryTags =>
@for(
(tagId, tag) <- allTags.sortBy(_._2.name.toLowerCase);
exists = libraryTags.map(_.tagId) contains tagId
){
<button class="btn btn-default@if(exists){ btn-success}" onclick="toggleTag(this)" data-library-id="@libraryId" data-tag-id="@tagId" title="@tag.note">@tag.name</button>
}
}
<button class="btn btn-default@if(classified){ btn-success}" onclick="toggleClassified(this)" data-library-id="@libraryId"></button>
</div>
</li>
}
</ul>

View File

@@ -0,0 +1,77 @@
@(idPrefix: String, list: Seq[GroupedDependency], selectorOption: Option[String], expandByDefault: Boolean = true, addButtons: Boolean = true)
@cpeHtmlId(cpe: String) = @{
cpe.getBytes("utf-8").mkString("-")
}
@for(dep <- list; depPrefix = s"$idPrefix-${dep.sha1}"){
<h3 class="library-identification" id="@depPrefix-head" data-toggle="collapse" data-target="#@depPrefix-details">
@libraryIdentification(dep, Some(cpe => s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}"), addLink = false, addButtons = addButtons)
@for(s <- dep.maxCvssScore) {
<span class="severity">
(<span title="highest vulnerability score" class="explained">@s</span>
× <span class="explained" title="affected project count">@dep.projects.size</span>
= <span class="explained score" title="total score">@dep.ysdssScore</span>)
(vulns:&nbsp;@dep.vulnerabilities.size)
</span>
}
@dep.cpeIdentifiers.toSeq match {
case Seq() => {}
case cpeIds => {
<a href="@routes.Statistics.searchVulnerableSoftware(cpeIds.map(_.name.split(':').take(4).mkString(":")).toSeq, None)" title="Search for known vulnerabilities"><span class="glyphicon glyphicon-flash"></span></a>
}
}
</h3>
@for(identifier <- dep.identifiers; cpe <- identifier.toCpeIdentifierOption ) {
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}")" class="collapse">@SuppressionXml.forCpe(dep, cpe)</div>
}
<div id="@depPrefix-details" class="collapse @if(expandByDefault){ in }">
@if(dep.descriptions.size > 1){
<div class="alert alert-warning">Multiple descriptions for this dependency!</div>
}
@for(descriptionParagraphs <- dep.parsedDescriptions){
<div class="description">
@for(descriptionParagraphLines <- descriptionParagraphs){
<p>
@for(line <- descriptionParagraphLines) {
@line<br>
}
</p>
}
</div>
}
<h4 data-toggle="collapse" data-target="#@depPrefix-evidence-details">Evidence</h4>
<table id="@depPrefix-evidence-details" class="collapse table table-bordered table-condensed">
<tr>
<th>confidence</th>
<th>evidence type</th>
<th>name</th>
<th>source</th>
<th>value</th>
</tr>
@for(ev <- dep.dependencies.keySet.map(_.evidenceCollected).flatten){
<tr>
<td>@ev.confidence
<td>@ev.evidenceType
<td>@ev.name
<td>@ev.source
<td>@ev.value
</tr>
}
</table>
<h4 data-toggle="collapse" data-target="#@depPrefix-projects-details">Affected projects (@dep.projects.size)</h4>
<ul id="@depPrefix-projects-details" class="collapse in">@for(p <- dep.projects.toIndexedSeq.sorted){<li>@friendlyProjectName(p)</li>}</ul>
<h4 data-toggle="collapse" data-target="#@depPrefix-vulnerabilities-details">Vulnerabilities (@dep.vulnerabilities.size)</h4>
<ul id="@depPrefix-vulnerabilities-details" class="collapse in">
@for(vuln <- dep.vulnerabilities.toSeq.sortBy(_.cvssScore.map(-_)); vulnPrefix = s"$depPrefix-vulnerabilities-details-${vuln.name}"){
<li>
<h5 data-toggle="collapse" data-target="#@vulnPrefix-details">@vuln.name <a href="@routes.Statistics.vulnerability(vuln.name, selectorOption)"><span class="glyphicon glyphicon-log-out"></span></a></h5>
<div id="@vulnPrefix-details" class="collapse">
@vulnerability("h6", s"$idPrefix-${dep.sha1}", vuln)
<h6 data-toggle="collapse" data-target="#@(s"$idPrefix-${dep.sha1}-suppression-cve-${vuln.name}")">CVE suppression</h6>
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cve-${vuln.name}")" class="collapse">@SuppressionXml.forVuln(dep, vuln)</div>
</div>
</li>
}
</ul>
</div>
}

View File

@@ -0,0 +1 @@
<b>all</b>

View File

@@ -0,0 +1,2 @@
@(currentProject: ReportInfo)
Project: <b>@friendlyProjectName(currentProject)</b>

View File

@@ -0,0 +1,2 @@
@(teamId: String)
Team: <b>@teamId</b>

View File

@@ -0,0 +1,3 @@
@(idPrefix: String)(ht: String)(name: String, description: String)(content: Html)
<@ht id="@idPrefix-@name-header" data-toggle="collapse" data-target="#@idPrefix-@name-details">@description</@ht>
<div id="@idPrefix-@name-details" class="collapse in">@content</div>

View File

@@ -0,0 +1,12 @@
@(name: String, id: String, collapse: Boolean = false, allowSnoozes: Boolean = true, versions: Map[String, Int])(list: Seq[GroupedDependency])(implicit rh: DefaultRequest, snoozes: SnoozesInfo, messages: Messages)
@conditionalList(list, name, id, collapse = collapse, allowSnoozes = allowSnoozes, versions = versions){
<table class="table">
@for(dep <- list){
<tr>
<td>@identifiers(dep.mavenIdentifiers)</td>
<td>@identifiers(dep.cpeIdentifiers)</td>
<td>@dep.descriptions</td>
</tr>
}
</table>
}

View File

@@ -0,0 +1,10 @@
package views.html
import com.ysoft.odc.{GroupedDependency, Vulnerability}
object SuppressionXml {
def forCpe(dep: GroupedDependency, cpe: String) = suppressionXmlPre(dep, <cpe>{cpe}</cpe>)
def forVuln(dep: GroupedDependency, vuln: Vulnerability) = suppressionXmlPre(dep, <cve>{vuln.name}</cve>)
}

View File

@@ -0,0 +1,9 @@
package views
import models.User
package object html {
type SortedMap[A, B] = scala.collection.SortedMap[A, B]
type UserAwareRequest[T] = controllers.AuthenticatedController#UserAwareRequest[T]
type DefaultRequest = UserAwareRequest[_]
}

View File

@@ -0,0 +1,3 @@
@(identifier: Identifier, addLink: Boolean = true)
@identifier.confidence.toString.toLowerCase:
@secureLink(if(addLink) identifier.url else ""){@identifier.name}

View File

@@ -0,0 +1,7 @@
@(identifiers: Set[Identifier])
@for(i <- identifiers){
<div class="identifier">
@identifier(i)
</div>
}

View File

@@ -0,0 +1,88 @@
@(
vulnerableDependencies: Seq[GroupedDependency],
unclassifiedDependencies: Seq[(Int, Library)],
warnings: Seq[Warning],
groupedDependencies: Seq[GroupedDependency],
dependenciesForLibraries: Map[PlainLibraryIdentifier, Set[GroupedDependency]],
allTags: Seq[(Int, LibraryTag)],
relatedDependenciesTags: Map[Int, Set[LibraryTagAssignment]],
librariesForTagsWithWarning: SortedMap[(Int, LibraryTag), Seq[(Int, Library)]],
lastRefreshTime: DateTime,
versions: Map[String, Int]
)(implicit req: DefaultRequest, snoozes: SnoozesInfo , messages: Messages)
@import com.ysoft.odc.Confidence
@import helper._
@main("Y Soft Dependency status"){
@form(routes.Application.purgeCache(versions, "index")){
@CSRF.formField
<button type="submit" class="btn btn-default">Purge cache</button> <span class="text-muted">(last update form build server: @lastRefreshTime)</span>
}
@conditionalList(warnings, "Warnings", "warnings", allowSnoozes = false, versions = versions){
@for(w <- warnings.sortBy(w => (-w.severity.id, w.id)); isLow <- Some(w.severity < WarningSeverity.Warning) ){
@defining(snoozes(s"warning-${w.id}")){ si =>
<div class="alert @(w.severity match {
case controllers.WarningSeverity.Error => "alert-danger"
case controllers.WarningSeverity.Warning => "alert-warning"
case controllers.WarningSeverity.Info => "alert-info"
}) @if(si.isSnoozed){ text-muted}" id="warning-@w.id">
<button data-toggle="collapse" class="btn btn-sm toggle-warning" data-target="#warning-@w.id-details, #warning-@w.id-snooze-button">
<span class="glyphicon glyphicon-grain" aria-hidden="true"></span>
</button>
@snoozeButton(s"warning-${w.id}", si, collapseByDefault = false)
@if(w.allowSnoozes){
@snoozeForm(s"warning-${w.id}", si, versions)
}
<div id="warning-@w.id-details" class="collapse @if(!si.shouldCollapse(default = isLow)){in}">
@w.html
@if(w.allowSnoozes){
@snoozesList(s"warning-${w.id}", si, versions)
}
</div>
<div class="clearfix"></div>
</div>
}
}
}
@conditionalList(vulnerableDependencies, s"Vulnerable dependencies ${(Seq(None) ++ vulnerableDependencies.map(_.maxCvssScore)).max.map{maxScore => s"(max CVSS: $maxScore)"}.getOrElse("")}", "vulnerable", versions = versions) {
@dependencyList("vulnerable", vulnerableDependencies.sortBy(d => (d.maxCvssScore.map(-_), d.cpeIdentifiers.map(_.toCpeIdentifierOption.get).toSeq.sorted.mkString(" "))), None)
}
@* groupedDependencyList("Unclassified dependencies", "unclassified")(unclassifiedDependencies) *@
@conditionalList(unclassifiedDependencies, "Unclassified dependencies", "unclassified", versions = versions){
@dependencyClassification(
prefix = "unclassified-dependency",
dependencies = unclassifiedDependencies,
allTags = allTags,
dependenciesTags = relatedDependenciesTags,
details = (libraryId: Int, lib: PlainLibraryIdentifier) => {
dependenciesForLibraries.get(lib).fold{Html("<p>No details</p>")} { deps =>
dependencyList(s"unclassified-library-$libraryId-details", deps.toSeq /*TODO: sort */, None)
}
}
)
}
@groupedDependencyList("Dependencies with low confidence of GAV", "gav-low-confidence", versions = versions)(groupedDependencies.filter(_.mavenIdentifiers.exists(_.confidence < Confidence.High)))
@for(((tagId, tag), libraries) <- librariesForTagsWithWarning){
@conditionalList(libraries, s"${tag.name}", s"tag-warning-$tagId", versions = versions){
@for(note <- tag.note){
<p>@note</p>
}
@dependencyClassification(
prefix = s"tag-warning-$tagId-list",
dependencies = libraries,
allTags = allTags,
dependenciesTags = relatedDependenciesTags,
details = (_, _) => Html("")
)
}
}
@* @groupedDependencyList("All dependencies", "all", collapse = true)(groupedDependencies) *@
}

View File

@@ -0,0 +1,16 @@
@(dep: GroupedDependency, suppressionXmlIdOption: Option[String => String] = None, addLink: Boolean = true, addButtons: Boolean = true)
@import com.ysoft.odc.Confidence
@import scala.math.Ordered.orderingToOrdered
@if(!dep.identifiers.exists(_.confidence >= Confidence.High)){
<span class="badge">file: @dep.fileNames.toSeq.sorted.mkString(", ")@if(addButtons){<span class="btn-xs library-identification-badge-hack">&nbsp;</span>}</span>
}
@for(id <- dep.identifiers.toSeq.sortBy(i => (i.confidence, i.identifierType, i.name, i.url)).reverse){
<span class="badge">
@identifier(id, addLink)
@for(cpe <- id.toCpeIdentifierOption; suppressionXmlId <- suppressionXmlIdOption; if addButtons){
<button class="btn btn-default btn-xs" data-toggle="collapse" data-target="#@suppressionXmlId(cpe)">×</button>
}
@if(addButtons && suppressionXmlIdOption.isDefined){<span class="btn-xs library-identification-badge-hack">&nbsp;</span>}
</span>
}

96
app/views/main.scala.html Normal file
View File

@@ -0,0 +1,96 @@
@import helper._
@(title: String, headExtension: Html = Html(""), projectsOption: Option[(ProjectsWithSelection, Option[String] => Call)] = None)(content: Html)(implicit header: DefaultRequest)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("lib/bootstrap/css/bootstrap.css")">
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("lib/bootstrap-datepicker/css/bootstrap-datepicker3.css")">
<link rel="stylesheet" type="text/css" href="@routes.Assets.versioned("css/main.css")">
<script type="text/javascript" src="@routes.Assets.versioned("lib/jquery/jquery.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/bootstrap/js/bootstrap.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/bootstrap-datepicker/js/bootstrap-datepicker.js")"></script>
<script type="text/javascript" src="@routes.Application.javascriptRoutes"></script>
<script type="text/javascript" src="@routes.Assets.versioned("js/main.js")"></script>
<script type="text/javascript">
if(!window.Routes){
window.Routes = {};
}
window.Routes.addTag = "@routes.Application.addTag";
window.Routes.removeTag = "@routes.Application.removeTag";
@* window.Routes.markClassified = "@routes.Application.markClassified"; *@
@* window.Routes.markUnclassified = "@routes.Application.markUnlassified"; *@
</script>
@if(!header.secure){
<script async defer type="text/javascript" src="@(routes.Application.testHttps(header.method == "GET").absoluteURL(secure = true))"></script>
}
@headExtension
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" id="header">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@* <a class="navbar-brand" href="#">YSSDC</a> *@
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="@routes.Application.index(Map())">Status</a></li>
<li><a href="@routes.Application.dependencies(None)">Tags</a></li>
<li><a href="@routes.Statistics.basic(None)">Tag statistics</a></li>
<li><a href="@routes.Statistics.vulnerabilities(None, None)">Vulnerabilities</a></li>
<li><a href="@routes.Statistics.vulnerableLibraries(None)">Vulnerable libraries</a></li>
<li>
@for((ProjectsWithSelection(filter, projects, teams), link) <- projectsOption){
<div id="project-selector">
<div class="dropdown">
<button class="btn @if(filter.filters){btn-warning}else{btn-primary} dropdown-toggle" type="button" data-toggle="dropdown">@filter.descriptionHtml
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="@link(None)"><i>all projects</i></a></li>
<li class="base-project"><a href="#">Teams</a></li>
@for(team <- teams){
<li><a href="@link(Some("team:"+team.id))" title="team leader: @team.leader">@team.name</a></li>
}
@for(report <- projects.ungroupedReportsInfo.toSeq.sortBy(p => p.projectName -> p.projectId -> p.subprojectNameOption)){
<li@if(report.subprojectNameOption.isEmpty){ class="base-project"}><a href="@link(Some("project:"+report.fullId))">@friendlyProjectName(report)</a></li>
}
</ul>
</div>
</div>
}
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>@header.identity.fold{
<a class="btn btn-default" href="@routes.AuthController.signIn()">Log in</a>
}{ user =>
@form(routes.AuthController.signOut()){
@CSRF.formField
<button type="submit" class="btn btn-warning">Logout @user.username</button>
}
}</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</nav>
<div class="container" id="main">
<h1>@title</h1>
@content
<hr>
That's all
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
@(url: String)(content: Html)
@url match{
case NormalUrlPattern(_ @ _*) => {<a href="@url">@content</a>}
case "" => {@content}
}

View File

@@ -0,0 +1,4 @@
@(id: String, si: SnoozeInfo, collapseByDefault: Boolean)
<button style="float: right;" id="@id-snooze-button" type="button" class="btn btn-sm collapse @if(!si.shouldCollapse(collapseByDefault)){in}" data-toggle="collapse" data-target="#@id-snoozing" aria-label="Snooze">
<span aria-hidden="true">&#128564;</span>
</button>

View File

@@ -0,0 +1,9 @@
@(id: String, si: SnoozeInfo, versions: Map[String, Int])(implicit rh: RequestHeader, snoozes: SnoozesInfo, messages: Messages)
@import helper._
@form((routes.Application.snooze(id, versions): Call).withFragment(id), 'id -> s"$id-snoozing", 'class -> s"snoozing collapse${if(si.shouldExpandForm) "in" else ""}") {
@CSRF.formField
@inputText(si.form("until"), '_label -> "Snooze until", Symbol("data-provide") -> "datepicker", Symbol("data-date-format") -> "dd-mm-yyyy")
@inputText(si.form("reason"), '_label -> "Reason")
<button type="submit" class="btn btn-default">Snooze!</button>
}

View File

@@ -0,0 +1,16 @@
@(unused_id: String, si: SnoozeInfo, versions: Map[String, Int])(implicit requestHeader: RequestHeader)
@import helper._
@if(si.isSnoozed){
<h3>Snooze details</h3>
<ul class="snooze-list">
@for((snoozeId, snooze) <- si.snoozes){
<li>
@form(routes.Application.unsnooze(snoozeId, versions)) {
@snooze.reason until @snooze.until
@CSRF.formField
<button type="submit" class="btn btn-danger">×</button>
}
</li>
}
</ul>
}

View File

@@ -0,0 +1,18 @@
@(
projectsWithSelection: ProjectsWithSelection,
allDependencies: Seq[GroupedDependency]
)(implicit header: DefaultRequest)
@main(
title = s"All libraries for ${projectsWithSelection.projectNameText}",
projectsOption = Some((projectsWithSelection, routes.Statistics.allLibraries(_)))
){
@dependencyList(
"all",
allDependencies.sortBy(_.identifiers.toIndexedSeq.sortBy(i => (i.confidence.id, i.identifierType, i.name)).mkString(", ")),
selectorOption = projectsWithSelection.selectorString,
expandByDefault = false,
addButtons = false
)
}

View File

@@ -0,0 +1,187 @@
@(
projectsWithSelection: ProjectsWithSelection,
tagStatistics: Seq[Statistics.TagStatistics],
parsedReports: DependencyCheckReportsParser.Result
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
@import com.ysoft.odc.CWE
@import controllers.Statistics.TagStatistics
@import play.api.libs.json.{JsNull, JsString}
@import scala.language.implicitConversions
@implicitTagStatistics(ts: TagStatistics) = @{ts.stats}
@he = {
<script type="text/javascript" src="@routes.Assets.versioned("lib/d3js/d3.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/jqplot/jquery.jqplot.min.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/jqplot/plugins/jqplot.barRenderer.min.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/jqplot/plugins/jqplot.categoryAxisRenderer.min.js")"></script>
<link type="text/css" rel="stylesheet" href="@routes.Assets.versioned("lib/jqplot/jquery.jqplot.min.css")">
<link type="text/css" rel="stylesheet" href="@routes.Assets.versioned("lib/tablesorter/css/theme.default.css")">
<script type="text/javascript" src="@routes.Assets.versioned("lib/tablesorter/js/jquery.tablesorter.min.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/StickyTableHeaders/js/jquery.stickytableheaders.min.js")"></script>
<script type="text/javascript">
$(function(){
$('.tablesorter').tablesorter();
$('.tablesorter').stickyTableHeaders({fixedOffset: $('#header')});
})
</script>
}
@plotData(frequency: Map[Option[CWE], Int]) = @{
import play.api.libs.json.Json._
val (ticks, details, values) = frequency.toSeq.sortBy{case (cweOption, _) => cweOption.map(c => c.numberOption -> c.name)}.map{
case (Some(cwe), freq) => (toJson(cwe.brief), toJson(cwe.name), freq)
case (None, freq) => (JsString("(none)"), JsNull, freq)
}.unzip3
toJson(Map(
"ticks" -> toJson(ticks),
"details" -> toJson(details),
"values" -> toJson(values)
))
}
@main(
title = s"statistics for ${projectsWithSelection.projectNameText}",
headExtension = he,
projectsOption = Some((projectsWithSelection, routes.Statistics.basic(_)))
){
All dependencies: @parsedReports.groupedDependencies.size <br>
Vulnerable dependencies: @parsedReports.vulnerableDependencies.size <br>
Vulnerabilities: @parsedReports.vulnerableDependencies.flatMap(_.vulnerabilities.map(_.name)).toSet.size<br>
Unique CPEs of vulnerable dependencies: @parsedReports.vulnerableDependencies.flatMap(_.cpeIdentifiers.map(_.toCpeIdentifierOption.get)).toSet.size <br>
Unique CPEs of all dependencies: @parsedReports.groupedDependencies.flatMap(_.cpeIdentifiers.map(_.toCpeIdentifierOption.get)).toSet.size <br>
@if(!projectsWithSelection.isProjectSpecified){
Multi-project dependencies: @parsedReports.groupedDependencies.filter(_.projects.size > 1).toSet.size <br>
}
<div id="weakness" data-data='@{plotData(Statistics.computeWeaknessesFrequency(parsedReports.groupedDependencies.flatMap(_.vulnerabilities).toSet))}'></div>
<script type="text/javascript">
var WeaknessIdentifier = function(brief, verbose){
this.brief = brief;
this.verbose = verbose;
};
WeaknessIdentifier.prototype.toString = function(){return "x"+this.brief;};
function initPlot(idPrefix, data){
var parentEl = $("#"+idPrefix);
var id = idPrefix+"-chart";
var detailsId = idPrefix+'-details';
if(parentEl.attr('data-initialized') == 'true'){
console.log('Not reinitializing the plot: ', idPrefix);
return;
}
console.log(parentEl);
parentEl.append($('<div>' ).attr({
"id": id,
"style" : "height: 300px; width: 900px;"
}));
parentEl.append($('<div>' ).attr({
"id": detailsId,
"style" : "height: 3ex; overflow: hidden;"
}));
var el = $("#"+id);
$.jqplot.config.enablePlugins = true;
data = data || JSON.parse(parentEl.attr('data-data'));
var s1 = data.values;
var ticks = data.ticks;
var details = data.details;
var plot1 = $.jqplot(id, [s1], {
animate: false, //!$.jqplot.use_excanvas, // Only animate if we're not using excanvas (not in IE 7 or IE 8)..
seriesDefaults:{
renderer:$.jqplot.BarRenderer,
pointLabels: { show: true }
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer,
ticks: ticks
}
},
highlighter: { show: false }
});
el.bind('jqplotDataClick',
function (ev, seriesIndex, pointIndex, data) {
//$('#info1').html('series: '+seriesIndex+', point: '+pointIndex+', data: '+data);
}
);
var detailsElement = $('#'+detailsId);
el.bind('jqplotDataUnhighlight', function(ev, seriesIndex, pointIndex, data){
detailsElement.text('');
});
el.bind('jqplotDataHighlight', function(ev, seriesIndex, pointIndex, data){
console.log('high', seriesIndex, pointIndex, data);
detailsElement.text((details[pointIndex]||"not described")+": "+s1[pointIndex]+"×");
});
el.attr('data-initialized', 'true');
};
$(document).ready(function(){
initPlot('weakness');
var n=0;
$('.stats').click(function(e){
console.log(e);
console.log(e.target);
var id = "modal-"+n;
n++;
var data = JSON.parse($(e.target).attr('data-data'));
var modalHeader = $('<div class="modal-header">' ).append($('<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>' )).append($('<h4 class="modal-title">Modal title</h4>'));
var modalBody = $('<div class="modal-body"></div>').attr({id: id});
var modalFooter = $('<div class="modal-footer"></div>');
var modalDialog = $('<div class="modal-dialog">').append($('<div class="modal-content">').append(modalHeader).append(modalBody).append(modalFooter));
var modal = $('<div class="modal fade">').append(modalDialog );
modalDialog.css({
width: '930px',
//marginLeft: '-465px'
margin: 'auto'
});
modal.on('shown.bs.modal', function(){initPlot(id, data)});
modal.on('hidden.bs.modal', function(){modal.remove()});
$(document.body ).append(modal);
modal.modal({keyboard: true});
console.log(id, data);
return false;
});
});
</script>
<table class="table table-striped tablesorter">
<thead>
<tr>
<th>tag name</th>
<th># of vulns</th>
<th>vulnerable</th>
<th>all</th>
<th>vulnerable/all</th>
<th>CPE %</th>
<th>vulnerable/CPE</th>
</tr>
</thead>
<tbody>
@for(s <- tagStatistics){
<tr>
<td title="@s.tag.note">
<a href="@routes.Statistics.vulnerabilities(projectsWithSelection.selectorString, Some(s.tagId))" target="_blank" class="stats">@s.tag.name</a>
@*<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30em;">@s.tag.note</div>*@
</td>
<td class="text-right">
@s.vulnerabilities.size
<button type="button" class="glyphicon glyphicon-signal btn btn-xs stats" @if(s.vulnerabilities.isEmpty){disabled="disabled"} data-data="@plotData(s.weaknessesFrequency)"></button>
</td>
<td class="text-right">@s.vulnerableDependencies.size</td>
<td class="text-right">@s.dependencies.size</td>
<td class="text-right">@(f"${s.vulnerableRatio*100}%2.2f")&nbsp;%</td>
<td class="text-right">@(f"${s.cpeRatio*100}%2.2f")&nbsp;%</td>
<td class="text-right">@(f"${s.vulnerableDependencies.size.toDouble*100.0/s.dependenciesWithCpe.size.toDouble}%2.2f")&nbsp;%</td>
</tr>
}
</tbody>
</table>
}

View File

@@ -0,0 +1,40 @@
@(
projectsWithSelection: ProjectsWithSelection,
tagOption: Option[(Int, LibraryTag)],
statistics: Statistics.LibDepStatistics
)(implicit messagesApi: MessagesApi, requestHeader: DefaultRequest)
@main(
title = s"details for ${projectsWithSelection.projectNameText}${tagOption.map(_._2.name).fold("")(" and tag "+_)}",
projectsOption = Some((projectsWithSelection, x => routes.Statistics.vulnerabilities(x, tagOption.map(_._1))))
){
We have @statistics.vulnerabilitiesToDependencies.size vulnerabilities
of @statistics.vulnerabilitiesToDependencies.flatMap(_._2).toSet.size dependencies (@statistics.vulnerabilitiesToDependencies.flatMap(_._2.flatMap(_.plainLibraryIdentifiers)).toSet.size libaries).
@if(!projectsWithSelection.isProjectSpecified){
They are affecting @statistics.vulnerabilitiesToDependencies.flatMap(_._2.flatMap(_.projects)).toSet.size projects.
}else{
Just one project is selected.
<div class="alert alert-warning">When a project is selected, YSVSS might differ, as it is computed over a subset of subprojects. As a result, order of vulnerabilities might differ from their order at all-projects view.</div>
}
<div class="help">
Vulnerabilities are sorted by number of affected projects multiplied by their severity. If the score is the same, then they are sorted by severity. If even this matches, they are sorted by name (which is related to vulnerability age).
</div>
@for((vulnerability, dependencies) <- statistics.vulnerabilitiesToDependencies.toSeq.sortBy{case (vuln, deps) =>
(
vuln.ysvssScore(deps).map(-_), // total score
vuln.cvssScore.map(-_), // CVSS score
vuln.name // make it deterministic
)
}){
<h2><a href="@routes.Statistics.vulnerability(vulnerability.name, projectsWithSelection.selectorString)">@vulnerability.name</a>
<span class="severity">
(<span class="explained" title="vulnerability CVSS score">@(vulnerability.cvss.score.getOrElse{"?"})</span> ×
<span class="explained" title="number of affected projects">@dependencies.flatMap(_.projects).toSet.size</span> =
<span class="explained score" title="total score">@(vulnerability.ysvssScore(dependencies).fold{"?"}{d => f"$d%2.2f"})</span>
)</span>
</h2>
<p>@vulnerability.description</p>
@* <p>@dependencies.map(_.identifiers)</p> *@
@* <p>@dependencies.flatMap(_.projects).toSet</p> *@
}
}

View File

@@ -0,0 +1,78 @@
@(
vulnsAndVersionOption: Option[(Traversable[Vulnerability], String)],
cpes: Seq[String],
isDbOld: Boolean
)(implicit header: DefaultRequest)
@import helper._
@main(
title = "Vulnerabilities for a libary"
){
<script type="text/javascript">
function versionChanged(that){
function addClass(o, cl){o.addClass(cl)};
function removeClass(o, cl){o.removeClass(cl)};
var differentVersion = $(that).attr('data-version') != that.value;
$('.checked-version').css({color: differentVersion ? 'red' : ''});
var classForDifferentVersion = differentVersion ?addClass :removeClass;
var classForSameVersion = differentVersion ?removeClass :addClass;
classForDifferentVersion($('#submit-btn'), 'btn-primary');
classForSameVersion($('#different-version-warning'), 'hidden');
}
</script>
@form(routes.Statistics.searchVulnerableSoftware(Seq(), None), 'onsubmit->
"""
|return (function(f){
| var selectedCpes = $(f.elements.versionlessCpes).filter(function(i, x){return x.checked;}).map(function(i, x){return x.value;}).toArray()
| if(selectedCpes.length == 0){
| alert("Choose at least one CPE, please!");
| return false;
| }
|})(this);
|""".stripMargin
){
<label>
Version:
<input
type="text" name="versionOption" id="version-field" value="@vulnsAndVersionOption.fold("")(_._2)"
data-version="@vulnsAndVersionOption.fold("")(_._2)"
onkeypress="versionChanged(this)"
onkeyup="versionChanged(this)"
onchange="versionChanged(this)"
onpaste="versionChanged(this)"
oncut="versionChanged(this)"
>
@for((_, version) <- vulnsAndVersionOption){
<span id="different-version-warning" class="hidden">Note that you are viewing results for version <strong>@version</strong>!</span>
}
</label>
<ul>
@for(cpe <- cpes){
<li><label><input type="checkbox" name="versionlessCpes" value="@cpe" checked> @cpe</label></li>
}
</ul>
<button type="submit" class="btn btn-default" id="submit-btn">Check</button>
}
@if(isDbOld){
<div class="alert alert-warning">The vulnerability database seems to be outdated. Result might be thus inaccurate. Contact the administrator, please.</div>
}
@vulnsAndVersionOption.fold{
Select desired version, please
}{ case (vulns, version) =>
@if(vulns.isEmpty){
<div class="alert alert-success">No known vulnerabilities for version <strong class="checked-version">@version</strong>.</div>
}else{
<div class="alert alert-warning">There @if(vulns.size == 1){is one known vulnerability}else{are some known vulnerabilities} for version <strong class="checked-version">@version</strong>. Consider @if(vulns.size==1){its}else{their} impact before using the library, please.</div>
@for(vuln <- vulns.toIndexedSeq.sortBy(v => (v.cvssScore.map(-_), v.name))){
<h2>@vuln.name</h2>
@vulnerability("h3", s"vulnerability-${vuln.name}-details", vuln)
}
}
}
@*if(vulnsAndVersionOption.isEmpty){ *@
<script type="text/javascript">
document.getElementById("version-field").focus();
</script>
@* } *@
}

View File

@@ -0,0 +1,43 @@
@(
projectsWithSelection: ProjectsWithSelection,
vulnerability: Vulnerability,
affectedProjects: Map[ReportInfo, Set[GroupedDependency]],
vulnerableDependencies: Set[GroupedDependency],
affectedLibraries: Set[PlainLibraryIdentifier]
)(implicit header: DefaultRequest)
@section = @{views.html.genericSection("vuln")("h2") _}
@main(
title = s"vulnerability ${vulnerability.name} for ${projectsWithSelection.projectNameText}",
projectsOption = Some((projectsWithSelection, p => routes.Statistics.vulnerability(vulnerability.name, p)))
) {
@if(projectsWithSelection.isProjectSpecified){
<div class="alert alert-warning">The vulnerability details are limited to some subset of projects.<br><a class="btn btn-default" href="@routes.Statistics.vulnerability(vulnerability.name, None)">Show it for all projects!</a></div>
}
@section("details", "Vulnerability details") {
@views.html.vulnerability("h2", "vuln-details", vulnerability)
}
@section("affected-libs", s"Unique affected libraries  without version number (${affectedLibraries.size})"){
<ul>
@for(lib <- affectedLibraries){
<li>@lib</li>
}
</ul>
}
@section("affected-deps", s"Unique affected dependencies (${vulnerableDependencies.size})"){
<ul>
@for(dep <- vulnerableDependencies){
<li class="library-identification">@libraryIdentification(dep)</li>
}
</ul>
}
@section("affected-projects", s"Affected projects (${affectedProjects.size} projects with ${affectedProjects.flatMap(_._2).size} occurrences)"){
@for((project, dependencies) <- affectedProjects.toSeq.sortBy(_._1)){
<h3><a href="@routes.Statistics.basic(Some("project:"+project.fullId))">@friendlyProjectName(project)</a> (@dependencies.size)</h3>
<ul>
@for(dep <- dependencies.toSeq){
<li class="library-identification">@libraryIdentification(dep)</li>
}
</ul>
}
}
}

View File

@@ -0,0 +1,24 @@
@(
projectsWithSelection: ProjectsWithSelection,
name: String
)(implicit header: DefaultRequest)
@main(
title = s"Unknown vulnerability $name for ${projectsWithSelection.projectNameText}",
projectsOption = Some((projectsWithSelection, p => routes.Statistics.vulnerability(name, p)))
){
<div class="alert alert-warning">Vulnerability <i>@name</i> is not found@if(projectsWithSelection.isProjectSpecified){ for selected project(s)}.</div>
<h2>Possible solutions</h2>
<ul class="solutions">
@if(projectsWithSelection.isProjectSpecified){
<li>
Maybe the vulnerability does not affect this project, but it might affect other projects.<br>
<a class="btn btn-success" href="@routes.Statistics.vulnerability(name, None)">Look at all the projects!</a>
</li>
}
<li>
Maybe the vulnerability does not affect any of the projects.<br>
<a href="https://web.nvd.nist.gov/view/vuln/detail?vulnId=@helper.urlEncode(name)" class="btn btn-default">Look at NVD</a>
</li>
</ul>
}

View File

@@ -0,0 +1,64 @@
@(
projectsWithSelection: ProjectsWithSelection,
vulnerableDependencies: Seq[GroupedDependency],
allDependenciesCount: Int
)(implicit header: DefaultRequest)
@main(
title = s"Vulnerable libraries for ${projectsWithSelection.projectNameText} (${vulnerableDependencies.size} deps, ${vulnerableDependencies.flatMap(_.cpeIdentifiers.map(_.toCpeIdentifierOption.get)).toSet.size} CPEs)",
projectsOption = Some((projectsWithSelection, routes.Statistics.vulnerableLibraries(_)))
){
<script type="text/javascript" src="@routes.Assets.versioned("lib/jqplot/jquery.jqplot.min.js")"></script>
<script type="text/javascript" src="@routes.Assets.versioned("lib/jqplot/plugins/jqplot.pieRenderer.min.js")"></script>
<h2>Plot</h2>
<div id="vulnerable-dependencies-chart"></div>
<script type="text/javascript">
$(document).ready(function(){
var data = [
['Vulnerable', (@(vulnerableDependencies.size))], ['No known vulnerability', (@(allDependenciesCount - vulnerableDependencies.size))]
];
var plot1 = jQuery.jqplot ('vulnerable-dependencies-chart', [data], {
seriesDefaults: {
// Make this a pie chart.
renderer: jQuery.jqplot.PieRenderer,
rendererOptions: {
// Put data labels on the pie slices.
// By default, labels show the percentage of the slice.
showDataLabels: true,
dataLabels: 'value',
startAngle: -90,
seriesColors: ['red', 'green'],
legendOptions: {
textColor: 'white'
}
}
},
legend: { show:true, location: 'e' }
});
});
</script>
<h2>List</h2>
<div class="help">
<p>Libraries are sorted:</p>
<ol>
<li>by total score (max vulnerability score × number of affected dependencies) if vulnerability score is defined for at least one vulnerability</li>
<li>by affected dependency count if the score above is not defined</li>
<li>by number of vulnerabilities</li>
<li>by affected project count</li>
</ol>
<p>Note that the number of affected projects is calculated from the current view, not from all projects (unless all projects are selected).</p>
</div>
@dependencyList(
"vulnerable",
vulnerableDependencies.sortBy(d => (
d.ysdssScore.map(-_), // total score is the king
if(d.ysdssScore.isEmpty) Some(-d.dependencies.size) else None, // more affected dependencies if no vulnerability has defined severity
-d.vulnerabilities.size, // more vulnerabilities
-d.projects.size, // more affected projects
d.cpeIdentifiers.map(_.toCpeIdentifierOption.get).toSeq.sorted.mkString(" ")) // at least make the order deterministic
),
selectorOption = projectsWithSelection.selectorString,
expandByDefault = false,
addButtons = false
)
}

View File

@@ -0,0 +1,12 @@
@(dep: GroupedDependency, details: scala.xml.Node)
<pre>&lt;?xml version="1.0" encoding="UTF-8"?>
@((
<suppressions xmlns="https://www.owasp.org/index.php/OWASP_Dependency_Check_Suppression">
<suppress>
<notes>file name: {dep.fileNames.mkString(" OR ")}</notes>
<sha1>{dep.sha1}</sha1>
{details}
</suppress>
</suppressions>
).toString)
</pre>

View File

@@ -0,0 +1,9 @@
@(f: Form[String])(implicit requestHeader: DefaultRequest, messages: Messages)
@import helper._
@main("Data import"){
@form(action = controllers.routes.Application.tagsImportAction()){
@CSRF.formField
@textarea(f("data"))
<button type="submit">Import</button>
}
}

View File

@@ -0,0 +1,35 @@
@(ht: String, idPrefix: String, vuln: Vulnerability)
@row[T](name: String, render: T => String = {(_:T).toString})(valueOption: Option[T]) = {
@for(value <- valueOption){
<tr>
<th>@name</th>
<td>@render(value)</td>
</tr>
}
}
@section = @{views.html.genericSection(idPrefix)(ht) _}
<table class="vuln-details">
@row("CWE")(vuln.cweOption)
@row("CVSS: score")(vuln.cvss.score)
@row("CVSS: authenticationr")(vuln.cvss.authenticationr)
@row("CVSS: availability impact")(vuln.cvss.availabilityImpact)
@row("CVSS: access vector")(vuln.cvss.accessVector)
@row("CVSS: integrity impact")(vuln.cvss.integrityImpact)
@row("CVSS: access complexity")(vuln.cvss.accessComplexity)
@row("CVSS: confidential impact")(vuln.cvss.confidentialImpact)
</table>
@vuln.description
@section("vuln-sw", "Vulnerable software"){
<ul id="@idPrefix-details">
@for(sw <- vuln.vulnerableSoftware){
<li>@sw.name@if(sw.allPreviousVersion){ and all previous versions}</li>
}
</ul>
}
@section("references", "References"){
<ul>
@for(reference <- vuln.references){
<li>@secureLink(reference.url){@reference.source: @reference.name}</li>
}
</ul>
}

View File

@@ -0,0 +1,7 @@
@(name: String, count: Int, items: Traversable[_])
@name (@count)<br>
<ul>
@for(i <- items){
<li>@i</li>
}
</ul>

View File

@@ -0,0 +1,7 @@
@(name: String, items: Seq[(ReportInfo, Html)])
<strong>@name:</strong>
<ul>
@for((name, i) <- items){
<li>@friendlyProjectName(name): @i</li>
}
</ul>

View File

@@ -0,0 +1,5 @@
@(emptyReports: Seq[Build], urlBase: String)
<strong>Following projects have produced no results:</strong>
@for(build <- emptyReports.toSeq.sortBy(_.projectName)){
<li>@secureLink(build.resultLink(urlBase)){@build.projectName}</li>
}

View File

@@ -0,0 +1,7 @@
@(failedReports: Set[Build], urlBase: String)
<strong>There are some reports that failed to build:</strong>
<ul>
@for(build <- failedReports.toSeq.sortBy(_.projectName)){
<li>@secureLink(build.resultLink(urlBase)){@build.projectName}</li>
}
</ul>

View File

@@ -0,0 +1,19 @@
@(errors: Map[String, Throwable])
<ul>
Some reports failed to be downloaded:
@for((project, e) <- errors){
<li>
@project: @e
@(e match {
case upickle.Invalid.Data(data, msg) => s"$msg (data: $data)"
case upickle.Invalid.Json(msg, input) => s"$msg (input: $input)"
case _ => ""
})
</li>
@{
play.api.Logger.logger.error(s"Project results of $project failed to parse.", e)
()
}
}
</ul>

View File

@@ -0,0 +1,10 @@
@(items: IndexedSeq[GroupedDependency])(implicit rh: DefaultRequest, snoozes: SnoozesInfo, messages: Messages)
(ignore this item)
@groupedDependencyList(name = "", id = s"grouped-dependencies-warning-${java.util.UUID.randomUUID.toString}", collapse = false, allowSnoozes = false, versions = Map())(list = items)
@for(groupedDependency <- items){
<li>
<strong>@groupedDependency.fileNames.toSeq.sorted</strong>
@identifiers(groupedDependency.identifiers)
@groupedDependency.dependencies.keySet.groupBy(_.evidenceCollected)
</li>
}

View File

@@ -0,0 +1,5 @@
@(reportsWithErrorMessages: Seq[Build], urlBase: String)
<strong>Following projects seem to contain some error messages in their logs:</strong>
@for(build <- reportsWithErrorMessages.toSeq.sortBy(_.projectName)){
<li>@secureLink(build.resultLink(urlBase)){@build.projectName}</li>
}

View File

@@ -0,0 +1,2 @@
@(text: String)
@text

View File

@@ -0,0 +1,7 @@
@(unknownIdentifierTypes: Set[String])
There are some unknown identifier types. These will not be handled by the application:
<ul>
@for(identifierType <- unknownIdentifierTypes.toSeq.sorted){
<li>@identifierType</li>
}
</ul>