mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-05-01 21:04:21 +02:00
Initial commit
This commit is contained in:
12
app/views/auth/signIn.scala.html
Normal file
12
app/views/auth/signIn.scala.html
Normal 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>
|
||||
}
|
||||
}
|
||||
18
app/views/conditionalList.scala.html
Normal file
18
app/views/conditionalList.scala.html
Normal 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){😴} @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>
|
||||
}
|
||||
}
|
||||
40
app/views/dependencies.scala.html
Normal file
40
app/views/dependencies.scala.html
Normal 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("")
|
||||
}
|
||||
)
|
||||
}
|
||||
40
app/views/dependencyClassification.scala.html
Normal file
40
app/views/dependencyClassification.scala.html
Normal 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>
|
||||
77
app/views/dependencyList.scala.html
Normal file
77
app/views/dependencyList.scala.html
Normal 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: @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>
|
||||
}
|
||||
1
app/views/filters/all.scala.html
Normal file
1
app/views/filters/all.scala.html
Normal file
@@ -0,0 +1 @@
|
||||
<b>all</b>
|
||||
2
app/views/filters/project.scala.html
Normal file
2
app/views/filters/project.scala.html
Normal file
@@ -0,0 +1,2 @@
|
||||
@(currentProject: ReportInfo)
|
||||
Project: <b>@friendlyProjectName(currentProject)</b>
|
||||
2
app/views/filters/team.scala.html
Normal file
2
app/views/filters/team.scala.html
Normal file
@@ -0,0 +1,2 @@
|
||||
@(teamId: String)
|
||||
Team: <b>@teamId</b>
|
||||
3
app/views/genericSection.scala.html
Normal file
3
app/views/genericSection.scala.html
Normal 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>
|
||||
12
app/views/groupedDependencyList.scala.html
Normal file
12
app/views/groupedDependencyList.scala.html
Normal 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>
|
||||
}
|
||||
10
app/views/html/SuppressionXml.scala
Normal file
10
app/views/html/SuppressionXml.scala
Normal 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>)
|
||||
|
||||
}
|
||||
9
app/views/html/package.scala
Normal file
9
app/views/html/package.scala
Normal 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[_]
|
||||
}
|
||||
3
app/views/identifier.scala.html
Normal file
3
app/views/identifier.scala.html
Normal file
@@ -0,0 +1,3 @@
|
||||
@(identifier: Identifier, addLink: Boolean = true)
|
||||
@identifier.confidence.toString.toLowerCase:
|
||||
@secureLink(if(addLink) identifier.url else ""){@identifier.name}
|
||||
7
app/views/identifiers.scala.html
Normal file
7
app/views/identifiers.scala.html
Normal file
@@ -0,0 +1,7 @@
|
||||
@(identifiers: Set[Identifier])
|
||||
|
||||
@for(i <- identifiers){
|
||||
<div class="identifier">
|
||||
@identifier(i)
|
||||
</div>
|
||||
}
|
||||
88
app/views/index.scala.html
Normal file
88
app/views/index.scala.html
Normal 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) *@
|
||||
|
||||
}
|
||||
16
app/views/libraryIdentification.scala.html
Normal file
16
app/views/libraryIdentification.scala.html
Normal 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"> </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"> </span>}
|
||||
</span>
|
||||
}
|
||||
96
app/views/main.scala.html
Normal file
96
app/views/main.scala.html
Normal 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>
|
||||
5
app/views/secureLink.scala.html
Normal file
5
app/views/secureLink.scala.html
Normal file
@@ -0,0 +1,5 @@
|
||||
@(url: String)(content: Html)
|
||||
@url match{
|
||||
case NormalUrlPattern(_ @ _*) => {<a href="@url">@content</a>}
|
||||
case "" => {@content}
|
||||
}
|
||||
4
app/views/snoozeButton.scala.html
Normal file
4
app/views/snoozeButton.scala.html
Normal 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">😴</span>
|
||||
</button>
|
||||
9
app/views/snoozeForm.scala.html
Normal file
9
app/views/snoozeForm.scala.html
Normal 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>
|
||||
}
|
||||
16
app/views/snoozesList.scala.html
Normal file
16
app/views/snoozesList.scala.html
Normal 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>
|
||||
}
|
||||
18
app/views/statistics/allLibraries.scala.html
Normal file
18
app/views/statistics/allLibraries.scala.html
Normal 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
|
||||
)
|
||||
|
||||
}
|
||||
187
app/views/statistics/basic.scala.html
Normal file
187
app/views/statistics/basic.scala.html
Normal 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">×</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") %</td>
|
||||
<td class="text-right">@(f"${s.cpeRatio*100}%2.2f") %</td>
|
||||
<td class="text-right">@(f"${s.vulnerableDependencies.size.toDouble*100.0/s.dependenciesWithCpe.size.toDouble}%2.2f") %</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
40
app/views/statistics/vulnerabilities.scala.html
Normal file
40
app/views/statistics/vulnerabilities.scala.html
Normal 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> *@
|
||||
}
|
||||
}
|
||||
78
app/views/statistics/vulnerabilitiesForLibrary.scala.html
Normal file
78
app/views/statistics/vulnerabilitiesForLibrary.scala.html
Normal 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>
|
||||
@* } *@
|
||||
|
||||
}
|
||||
43
app/views/statistics/vulnerability.scala.html
Normal file
43
app/views/statistics/vulnerability.scala.html
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
24
app/views/statistics/vulnerabilityNotFound.scala.html
Normal file
24
app/views/statistics/vulnerabilityNotFound.scala.html
Normal 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>
|
||||
}
|
||||
64
app/views/statistics/vulnerableLibraries.scala.html
Normal file
64
app/views/statistics/vulnerableLibraries.scala.html
Normal 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
|
||||
)
|
||||
}
|
||||
12
app/views/suppressionXmlPre.scala.html
Normal file
12
app/views/suppressionXmlPre.scala.html
Normal file
@@ -0,0 +1,12 @@
|
||||
@(dep: GroupedDependency, details: scala.xml.Node)
|
||||
<pre><?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>
|
||||
9
app/views/tagsImport.scala.html
Normal file
9
app/views/tagsImport.scala.html
Normal 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>
|
||||
}
|
||||
}
|
||||
35
app/views/vulnerability.scala.html
Normal file
35
app/views/vulnerability.scala.html
Normal 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>
|
||||
}
|
||||
7
app/views/warnings/badGroupedDependencies.scala.html
Normal file
7
app/views/warnings/badGroupedDependencies.scala.html
Normal file
@@ -0,0 +1,7 @@
|
||||
@(name: String, count: Int, items: Traversable[_])
|
||||
@name (@count)<br>
|
||||
<ul>
|
||||
@for(i <- items){
|
||||
<li>@i</li>
|
||||
}
|
||||
</ul>
|
||||
7
app/views/warnings/badValues.scala.html
Normal file
7
app/views/warnings/badValues.scala.html
Normal 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>
|
||||
5
app/views/warnings/emptyResults.scala.html
Normal file
5
app/views/warnings/emptyResults.scala.html
Normal 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>
|
||||
}
|
||||
7
app/views/warnings/failedReports.scala.html
Normal file
7
app/views/warnings/failedReports.scala.html
Normal 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>
|
||||
19
app/views/warnings/failedResults.scala.html
Normal file
19
app/views/warnings/failedResults.scala.html
Normal 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>
|
||||
10
app/views/warnings/groupedDependencies.scala.html
Normal file
10
app/views/warnings/groupedDependencies.scala.html
Normal 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>
|
||||
}
|
||||
5
app/views/warnings/resultsWithErrorMessages.scala.html
Normal file
5
app/views/warnings/resultsWithErrorMessages.scala.html
Normal 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>
|
||||
}
|
||||
2
app/views/warnings/textWarning.scala.html
Normal file
2
app/views/warnings/textWarning.scala.html
Normal file
@@ -0,0 +1,2 @@
|
||||
@(text: String)
|
||||
@text
|
||||
7
app/views/warnings/unknownIdentifierType.scala.html
Normal file
7
app/views/warnings/unknownIdentifierType.scala.html
Normal 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>
|
||||
Reference in New Issue
Block a user