internal/frontend: improve display of vul affected versions
Instead of "Introduced" and "Fixed" columns, construct a more readable
string that describes the vulnerable versions.
Change-Id: Iac7435a0b372bde23f0c6177490b39f16505b5a7
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/411077
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/frontend/vulns.go b/internal/frontend/vulns.go
index ffb22a5..b2c1253 100644
--- a/internal/frontend/vulns.go
+++ b/internal/frontend/vulns.go
@@ -8,6 +8,7 @@
"fmt"
"net/http"
"sort"
+ "strings"
"golang.org/x/mod/semver"
"golang.org/x/pkgsite/internal"
@@ -74,7 +75,13 @@
// VulnPage holds the information for a page that displays a single vuln entry.
type VulnPage struct {
basePage
- Entry *osv.Entry
+ Entry *osv.Entry
+ AffectedPackages []*AffectedPackage
+}
+
+type AffectedPackage struct {
+ Path string // Package.Name in the osv.Entry
+ Versions string
}
func entryVuln(e *osv.Entry, packagePath, version string) (Vuln, bool) {
@@ -140,8 +147,8 @@
if entry == nil {
return nil, derrors.NotFound
}
- addVersionPrefixes(entry)
- return &VulnPage{Entry: entry}, nil
+ affs := affectedPackages(entry)
+ return &VulnPage{Entry: entry, AffectedPackages: affs}, nil
}
func newVulnListPage(client vulnc.Client) (*VulnListPage, error) {
@@ -177,6 +184,78 @@
return &VulnListPage{Entries: entries}, nil
}
+// A pair is like an osv.Range, but each pair is a self-contained 2-tuple
+// (introduced version, fixed version).
+type pair struct {
+ intro, fixed string
+}
+
+// collectRangePairs turns a slice of osv Ranges into a more manageable slice of
+// formatted version pairs.
+func collectRangePairs(a osv.Affected) []pair {
+ var (
+ ps []pair
+ p pair
+ prefix string
+ )
+ if stdlib.Contains(a.Package.Name) {
+ prefix = "go"
+ } else {
+ prefix = "v"
+ }
+ for _, r := range a.Ranges {
+ isSemver := r.Type == osv.TypeSemver
+ for _, v := range r.Events {
+ if v.Introduced != "" {
+ if p.intro != "" {
+ // We expected Introduced and Fixed to alternate, but they don't.
+ // Keep going, ignoring the first Introduced.
+ }
+ p.intro = v.Introduced
+ if p.intro == "0" {
+ p.intro = ""
+ }
+ if isSemver && p.intro != "" {
+ p.intro = prefix + p.intro
+ }
+ }
+ if v.Fixed != "" {
+ p.fixed = v.Fixed
+ if isSemver && p.fixed != "" {
+ p.fixed = prefix + p.fixed
+ }
+ ps = append(ps, p)
+ p = pair{}
+ }
+ }
+ }
+ return ps
+}
+
+func affectedPackages(e *osv.Entry) []*AffectedPackage {
+ var affs []*AffectedPackage
+ for _, a := range e.Affected {
+ pairs := collectRangePairs(a)
+ var vs []string
+ for _, p := range pairs {
+ var s string
+ if p.intro == "" {
+ s = p.fixed + " and earlier"
+ } else if p.fixed == "" {
+ s = p.intro + " and later"
+ } else {
+ s = p.intro + " - " + p.fixed
+ }
+ vs = append(vs, s)
+ }
+ affs = append(affs, &AffectedPackage{
+ Path: a.Package.Name,
+ Versions: strings.Join(vs, ", "),
+ })
+ }
+ return affs
+}
+
func addVersionPrefix(semver, packagePath string) (res string) {
if semver == "" {
return ""
@@ -186,16 +265,3 @@
}
return "v" + semver
}
-
-func addVersionPrefixes(e *osv.Entry) {
- for i, a := range e.Affected {
- for j, r := range a.Ranges {
- if r.Type == osv.TypeSemver {
- for k, v := range r.Events {
- e.Affected[i].Ranges[j].Events[k].Introduced = addVersionPrefix(v.Introduced, a.Package.Name)
- e.Affected[i].Ranges[j].Events[k].Fixed = addVersionPrefix(v.Fixed, a.Package.Name)
- }
- }
- }
- }
-}
diff --git a/internal/frontend/vulns_test.go b/internal/frontend/vulns_test.go
index 6fdd240..82699f5 100644
--- a/internal/frontend/vulns_test.go
+++ b/internal/frontend/vulns_test.go
@@ -7,6 +7,7 @@
import (
"errors"
"fmt"
+ "reflect"
"testing"
"github.com/google/go-cmp/cmp"
@@ -96,63 +97,6 @@
}
}
-func TestAddVersionPrefixes(t *testing.T) {
- in := &osv.Entry{
- ID: "id",
- Affected: []osv.Affected{
- {
- Package: osv.Package{Name: "a.com/b"},
- Ranges: osv.Affects{{
- Type: osv.TypeSemver, Events: []osv.RangeEvent{
- {Introduced: "1.2.3"},
- {Fixed: "1.4.0"},
- {Introduced: "2.1.0", Fixed: "2.2.1"},
- }},
- },
- },
- {
- Package: osv.Package{Name: "crypto/math"},
- Ranges: osv.Affects{{
- Type: osv.TypeSemver, Events: []osv.RangeEvent{
- {Introduced: "1.2.3"},
- {Fixed: "1.4.0"},
- {Introduced: "2.1.0", Fixed: "2.2.1"},
- }},
- },
- },
- },
- }
- want := &osv.Entry{
- ID: "id",
- Affected: []osv.Affected{
- {
- Package: osv.Package{Name: "a.com/b"},
- Ranges: osv.Affects{{
- Type: osv.TypeSemver, Events: []osv.RangeEvent{
- {Introduced: "v1.2.3"},
- {Fixed: "v1.4.0"},
- {Introduced: "v2.1.0", Fixed: "v2.2.1"},
- }},
- },
- },
- {
- Package: osv.Package{Name: "crypto/math"},
- Ranges: osv.Affects{{
- Type: osv.TypeSemver, Events: []osv.RangeEvent{
- {Introduced: "go1.2.3"},
- {Fixed: "go1.4.0"},
- {Introduced: "go2.1.0", Fixed: "go2.2.1"},
- }},
- },
- },
- },
- }
- addVersionPrefixes(in)
- if diff := cmp.Diff(want, in); diff != "" {
- t.Errorf("mismatch (-want, +got)\n%s", diff)
- }
-}
-
type vulndbTestClient struct {
vulnc.Client
entries []*osv.Entry
@@ -178,3 +122,28 @@
}
return ids, nil
}
+
+func TestCollectRangePairs(t *testing.T) {
+ in := osv.Affected{
+ Package: osv.Package{Name: "github.com/a/b"},
+ Ranges: osv.Affects{
+ {Type: osv.TypeSemver, Events: []osv.RangeEvent{{Introduced: "", Fixed: "0.5"}}},
+ {Type: osv.TypeSemver, Events: []osv.RangeEvent{
+ {Introduced: "1.2"}, {Fixed: "1.5"},
+ {Introduced: "2.1", Fixed: "2.3"},
+ }},
+ {Type: osv.TypeGit, Events: []osv.RangeEvent{{Introduced: "a", Fixed: "b"}}},
+ },
+ }
+ got := collectRangePairs(in)
+ want := []pair{
+ {"", "v0.5"},
+ {"v1.2", "v1.5"},
+ {"v2.1", "v2.3"},
+ {"a", "b"},
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("\ngot %+v\nwant %+v", got, want)
+ }
+
+}
diff --git a/static/frontend/vuln/entry/entry.tmpl b/static/frontend/vuln/entry/entry.tmpl
index c4ad740..5cb2636 100644
--- a/static/frontend/vuln/entry/entry.tmpl
+++ b/static/frontend/vuln/entry/entry.tmpl
@@ -13,6 +13,7 @@
{{end}}
{{define "main-content"}}
+ {{/* . is internal/frontend.VulnPage */}}
<nav class="go-Breadcrumb" aria-label="Breadcrumb">
<ol>
<li>
@@ -25,45 +26,43 @@
</nav>
<h1 class="Vuln-title">Vulnerability Report: {{.Entry.ID}}</h1>
{{template "vuln-details" .Entry}}
- {{template "entry" .Entry}}
+ <div class="VulnEntry">
+ {{template "affected" .AffectedPackages}}
+ {{template "entry" .Entry}}
+ </div>
+{{end}}
+
+{{define "affected"}}
+ <h2>Affected Packages</h2>
+ <table class="VulnEntry-table">
+ <thead>
+ <tr>
+ <th>Package</th>
+ <th>Affected Versions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{range .}}
+ <tr>
+ <td><a href="/{{.Path}}">{{.Path}}</a></td>
+ <td>{{.Versions}}</td>
+ </tr>
+ {{end}}
+ </tbody>
+ </table>
{{end}}
{{define "entry"}}
- <div class="VulnEntry">
- <h2>Affected Packages</h2>
- <table class="VulnEntry-table">
- <thead>
- <tr>
- <th>Package</th>
- <th>Introduced</th>
- <th>Fixed</th>
- </tr>
- </thead>
- <tbody>
- {{range .Affected}}
- <tr>
- <td><a href="/{{.Package.Name}}">{{.Package.Name}}</a></td>
- <td>
- {{range .Ranges}}{{range .Events}}
- <div>{{if not (eq .Introduced "0")}}{{.Introduced}}{{end}}</div>
- {{end}}{{end}}
- </td>
- <td>{{range .Ranges}}{{range .Events}}<div>{{.Fixed}}</div>{{end}}{{end}}</td>
- </tr>
- {{end}}
- </tbody>
- </table>
- {{if .Aliases}}
- <h2>Aliases</h2>
- <ul>
- {{range .Aliases}}<li>{{.}}</li>{{end}}
- </ul>
- {{end}}
- {{if .References}}
- <h2>References</h2>
- <ul class="VulnEntry-referenceList">
- {{range .References}}<li><a href="{{.URL}}">{{.URL}}</a></li>{{end}}
- </ul>
- {{end}}
- </div>
+ {{if .Aliases}}
+ <h2>Aliases</h2>
+ <ul>
+ {{range .Aliases}}<li>{{.}}</li>{{end}}
+ </ul>
+ {{end}}
+ {{if .References}}
+ <h2>References</h2>
+ <ul class="VulnEntry-referenceList">
+ {{range .References}}<li><a href="{{.URL}}">{{.URL}}</a></li>{{end}}
+ </ul>
+ {{end}}
{{end}}