internal,static: alias IDs on vuln entry pages link to relevant reports

Links for aliases of GitHub and MITRE link to their reports.
Additional sources will need to be added as integrations are
added.

Change-Id: I8b1d661f275fcc121f56ad5f17a02684ffbc91d1
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/424554
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Julie Qiu <julieqiu@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/frontend/vulns.go b/internal/frontend/vulns.go
index 35cdd69..6c5e4cc 100644
--- a/internal/frontend/vulns.go
+++ b/internal/frontend/vulns.go
@@ -19,6 +19,12 @@
 	"golang.org/x/vuln/osv"
 )
 
+const (
+	githubAdvisoryUrlPrefix = "https://github.com/advisories/"
+	mitreAdvisoryUrlPrefix  = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
+	nistAdvisoryUrlPrefix   = "https://nvd.nist.gov/vuln/detail/"
+)
+
 // A Vuln contains information to display about a vulnerability.
 type Vuln struct {
 	// The vulndb ID.
@@ -77,6 +83,7 @@
 	basePage
 	Entry            *osv.Entry
 	AffectedPackages []*AffectedPackage
+	AliasLinks       []link
 }
 
 type AffectedPackage struct {
@@ -162,7 +169,8 @@
 		return nil, derrors.NotFound
 	}
 	affs := affectedPackages(entry)
-	return &VulnPage{Entry: entry, AffectedPackages: affs}, nil
+	links := aliasLinks(entry)
+	return &VulnPage{Entry: entry, AffectedPackages: affs, AliasLinks: links}, nil
 }
 
 func newVulnListPage(client vulnc.Client) (*VulnListPage, error) {
@@ -269,6 +277,30 @@
 	return affs
 }
 
+// aliasLinks generates links to reference pages for vuln aliases.
+func aliasLinks(e *osv.Entry) []link {
+	var cveRef string
+	for _, ref := range e.References {
+		if strings.HasPrefix(ref.URL, nistAdvisoryUrlPrefix) || strings.HasPrefix(ref.URL, mitreAdvisoryUrlPrefix) {
+			cveRef = ref.URL
+			break
+		}
+	}
+	var links []link
+	for _, a := range e.Aliases {
+		prefix, _, _ := strings.Cut(a, "-")
+		switch prefix {
+		case "CVE":
+			links = append(links, link{Body: a, Href: cveRef})
+		case "GHSA":
+			links = append(links, link{Body: a, Href: githubAdvisoryUrlPrefix + a})
+		default:
+			links = append(links, link{Body: a})
+		}
+	}
+	return links
+}
+
 func addVersionPrefix(semver, packagePath string) (res string) {
 	if semver == "" {
 		return ""
diff --git a/internal/frontend/vulns_test.go b/internal/frontend/vulns_test.go
index 82699f5..96efa79 100644
--- a/internal/frontend/vulns_test.go
+++ b/internal/frontend/vulns_test.go
@@ -147,3 +147,43 @@
 	}
 
 }
+
+func Test_aliasLinks(t *testing.T) {
+	type args struct {
+		e *osv.Entry
+	}
+	tests := []struct {
+		name string
+		args args
+		want []link
+	}{
+		{
+			"reserved cve",
+			args{&osv.Entry{Aliases: []string{"CVE-0000-00000"}}},
+			[]link{{Body: "CVE-0000-00000"}},
+		},
+		{
+			"nist",
+			args{&osv.Entry{Aliases: []string{"CVE-0000-00000"}, References: []osv.Reference{{Type: "ADVISORY", URL: nistAdvisoryUrlPrefix + "CVE-0000-00000"}}}},
+			[]link{{Body: "CVE-0000-00000", Href: nistAdvisoryUrlPrefix + "CVE-0000-00000"}},
+		},
+		{
+			"github",
+			args{&osv.Entry{Aliases: []string{"GHSA-zz00-zzz0-0zz0"}}},
+			[]link{{Body: "GHSA-zz00-zzz0-0zz0", Href: githubAdvisoryUrlPrefix + "GHSA-zz00-zzz0-0zz0"}},
+		},
+		{
+			"empty link",
+			args{&osv.Entry{Aliases: []string{"NA-0000"}}},
+			[]link{{Body: "NA-0000"}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := aliasLinks(tt.args.e)
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Errorf("mismatch(-want, +got): %s", diff)
+			}
+		})
+	}
+}
diff --git a/static/frontend/vuln/entry/entry.css b/static/frontend/vuln/entry/entry.css
index 6a0e501..394bbed 100644
--- a/static/frontend/vuln/entry/entry.css
+++ b/static/frontend/vuln/entry/entry.css
@@ -35,7 +35,8 @@
 .VulnEntry-table tbody > tr {
   border-bottom: var(--border);
 }
-.VulnEntry-referenceList {
+.VulnEntry-referenceList,
+.VulnEntry-aliases {
   line-height: 1.75rem;
   word-break: break-all;
 }
diff --git a/static/frontend/vuln/entry/entry.min.css b/static/frontend/vuln/entry/entry.min.css
index def7f0f..5b6deda 100644
--- a/static/frontend/vuln/entry/entry.min.css
+++ b/static/frontend/vuln/entry/entry.min.css
@@ -3,5 +3,5 @@
  * Use of this source code is governed by a BSD-style
  * license that can be found in the LICENSE file.
  */
-.Vuln-alias{display:none}.VulnEntry{display:flex;flex-direction:column;gap:1rem;margin-top:.5rem}.VulnEntry h2{font-size:1.25rem}.VulnEntry-table{margin-bottom:.5rem;text-align:left;width:100%}.VulnEntry-table thead{background-color:var(--color-background-accented)}.VulnEntry-table tbody{word-break:break-all}.VulnEntry-table td,.VulnEntry-table th{padding:.75rem 1rem}.VulnEntry-table tbody>tr{border-bottom:var(--border)}.VulnEntry-referenceList{line-height:1.75rem;word-break:break-all}
+.Vuln-alias{display:none}.VulnEntry{display:flex;flex-direction:column;gap:1rem;margin-top:.5rem}.VulnEntry h2{font-size:1.25rem}.VulnEntry-table{margin-bottom:.5rem;text-align:left;width:100%}.VulnEntry-table thead{background-color:var(--color-background-accented)}.VulnEntry-table tbody{word-break:break-all}.VulnEntry-table td,.VulnEntry-table th{padding:.75rem 1rem}.VulnEntry-table tbody>tr{border-bottom:var(--border)}.VulnEntry-referenceList,.VulnEntry-aliases{line-height:1.75rem;word-break:break-all}
 /*# sourceMappingURL=entry.min.css.map */
diff --git a/static/frontend/vuln/entry/entry.min.css.map b/static/frontend/vuln/entry/entry.min.css.map
index a72dbb3..eb9273d 100644
--- a/static/frontend/vuln/entry/entry.min.css.map
+++ b/static/frontend/vuln/entry/entry.min.css.map
@@ -1,7 +1,7 @@
 {
   "version": 3,
   "sources": ["entry.css"],
-  "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.Vuln-alias {\n  display: none;\n}\n\n.VulnEntry {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  margin-top: 0.5rem;\n}\n.VulnEntry h2 {\n  font-size: 1.25rem;\n}\n.VulnEntry-table {\n  margin-bottom: 0.5rem;\n  text-align: left;\n  width: 100%;\n}\n.VulnEntry-table thead {\n  background-color: var(--color-background-accented);\n}\n.VulnEntry-table tbody {\n  word-break: break-all;\n}\n.VulnEntry-table td,\n.VulnEntry-table th {\n  padding: 0.75rem 1rem;\n}\n.VulnEntry-table tbody > tr {\n  border-bottom: var(--border);\n}\n.VulnEntry-referenceList {\n  line-height: 1.75rem;\n  word-break: break-all;\n}\n"],
-  "mappings": ";;;;;AAMA,YACE,aAGF,WACE,aACA,sBACA,SACA,iBAEF,cACE,kBAEF,iBACE,oBACA,gBACA,WAEF,uBACE,kDAEF,uBACE,qBAEF,wCA9BA,oBAkCA,0BACE,4BAEF,yBACE,oBACA",
+  "sourcesContent": ["/*\n * Copyright 2021 The Go Authors. All rights reserved.\n * Use of this source code is governed by a BSD-style\n * license that can be found in the LICENSE file.\n */\n\n.Vuln-alias {\n  display: none;\n}\n\n.VulnEntry {\n  display: flex;\n  flex-direction: column;\n  gap: 1rem;\n  margin-top: 0.5rem;\n}\n.VulnEntry h2 {\n  font-size: 1.25rem;\n}\n.VulnEntry-table {\n  margin-bottom: 0.5rem;\n  text-align: left;\n  width: 100%;\n}\n.VulnEntry-table thead {\n  background-color: var(--color-background-accented);\n}\n.VulnEntry-table tbody {\n  word-break: break-all;\n}\n.VulnEntry-table td,\n.VulnEntry-table th {\n  padding: 0.75rem 1rem;\n}\n.VulnEntry-table tbody > tr {\n  border-bottom: var(--border);\n}\n.VulnEntry-referenceList,\n.VulnEntry-aliases {\n  line-height: 1.75rem;\n  word-break: break-all;\n}\n"],
+  "mappings": ";;;;;AAMA,YACE,aAGF,WACE,aACA,sBACA,SACA,iBAEF,cACE,kBAEF,iBACE,oBACA,gBACA,WAEF,uBACE,kDAEF,uBACE,qBAEF,wCA9BA,oBAkCA,0BACE,4BAEF,4CAEE,oBACA",
   "names": []
 }
diff --git a/static/frontend/vuln/entry/entry.tmpl b/static/frontend/vuln/entry/entry.tmpl
index 2510ef7..7cfa25f 100644
--- a/static/frontend/vuln/entry/entry.tmpl
+++ b/static/frontend/vuln/entry/entry.tmpl
@@ -31,7 +31,7 @@
   {{template "vuln-details" .Entry}}
   <div class="VulnEntry">
     {{template "affected" .AffectedPackages}}
-    {{template "entry" .Entry}}
+    {{template "entry" .}}
   </div>
 {{end}}
 
@@ -56,22 +56,29 @@
 {{end}}
 
 {{define "entry"}}
-  {{if .Aliases}}
+  {{$e := .Entry}}
+  {{if .AliasLinks}}
     <h2>Aliases</h2>
-    <ul>
-      {{range .Aliases}}<li>{{.}}</li>{{end}}
+    <ul class="VulnEntry-aliases">
+      {{range .AliasLinks}}<li>
+        {{if .Href}}
+          <a href="{{.Href}}" target="_blank" rel="noopener">{{.Body}}</a>
+        {{else}}
+          {{.Body}}
+        {{end}}
+      </li>{{end}}
     </ul>
   {{end}}
-  {{if .References}}
+  {{if $e.References}}
     <h2>References</h2>
     <ul class="VulnEntry-referenceList">
-      {{range .References}}<li><a href="{{.URL}}" target="_blank" rel="noopener">{{.URL}}</a></li>{{end}}
+      {{range $e.References}}<li><a href="{{.URL}}" target="_blank" rel="noopener">{{.URL}}</a></li>{{end}}
     </ul>
   {{end}}
   <h2>Feedback</h2>
   <div>
     See anything missing or incorrect?
-    <a target="_blank" rel="noopener" href="https://github.com/golang/vulndb/issues/new?assignees=&labels=Needs+Triage%2CSuggested+Edit&template=suggest_edit.yaml&title=x%2Fvulndb%3A+suggestion+regarding+{{.ID}}&report={{.ID}}">
+    <a target="_blank" rel="noopener" href="https://github.com/golang/vulndb/issues/new?assignees=&labels=Needs+Triage%2CSuggested+Edit&template=suggest_edit.yaml&title=x%2Fvulndb%3A+suggestion+regarding+{{$e.ID}}&report={{$e.ID}}">
       Suggest an edit to this report.
     </a>
   </div>