Skip to content

Commit e664411

Browse files
committed
feat : Option to filter Vunerable and Non Vulnerable Packages
Signed-off-by: Rishi Garg <[email protected]>
1 parent fabe035 commit e664411

File tree

4 files changed

+83
-4
lines changed

4 files changed

+83
-4
lines changed

vulnerabilities/forms.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,36 @@
1414

1515

1616
class PackageSearchForm(forms.Form):
17-
1817
search = forms.CharField(
1918
required=True,
2019
widget=forms.TextInput(
21-
attrs={"placeholder": "Package name, purl or purl fragment"},
20+
attrs={
21+
"placeholder": "Package name, purl or purl fragment",
22+
},
23+
),
24+
)
25+
type = forms.CharField(required=False, max_length=50)
26+
vulnerable_only = forms.ChoiceField(
27+
required=False,
28+
choices=(
29+
("", "All Packages"),
30+
("true", "Vulnerable Only"),
31+
("false", "Non-Vulnerable Only"),
2232
),
2333
)
2434

35+
def clean_search(self):
36+
"""Sanitize the search input which provide extra layer of protection from XSS attacks"""
37+
search = self.cleaned_data["search"].strip()
38+
if not search:
39+
raise forms.ValidationError("Search field cannot be empty")
40+
return search
41+
42+
def clean_type(self):
43+
"""Sanitize the type input which provide extra layer of protection from XSS attacks"""
44+
pkg_type = self.cleaned_data["type"].strip()
45+
return pkg_type
46+
2547

2648
class VulnerabilitySearchForm(forms.Form):
2749

vulnerabilities/templates/packages.html

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
<div>
1919
{{ page_obj.paginator.count|intcomma }} results
2020
</div>
21+
<select name="vulnerable_only" class="select" id="vulnerable-select">
22+
<option value="">All Packages</option>
23+
<option value="true" {% if request.GET.vulnerable_only == 'true' %}selected{% endif %}>Vulnerable Only</option>
24+
<option value="false" {% if request.GET.vulnerable_only == 'false' %}selected{% endif %}>Non-Vulnerable Only</option>
25+
</select>
26+
2127
{% if is_paginated %}
2228
{% include 'includes/pagination.html' with page_obj=page_obj %}
2329
{% endif %}
@@ -81,4 +87,28 @@
8187

8288
</section>
8389
{% endif %}
84-
{% endblock %}
90+
<script>
91+
// This script is used to update the URL when the user selects a value from the vulnerable_only dropdown menu.
92+
// It also sanitize the URL to prevent XSS attacks.
93+
document.getElementById('vulnerable-select').addEventListener('change', function() {
94+
let searchParams = new URLSearchParams(window.location.search);
95+
let selectedValue = this.value.replace(/[^a-zA-Z0-9]/g, '');
96+
let searchTerm = searchParams.get('search');
97+
if (searchTerm) {
98+
searchTerm = encodeURIComponent(searchTerm);
99+
}
100+
if (selectedValue) {
101+
searchParams.set('vulnerable_only', selectedValue);
102+
} else {
103+
searchParams.delete('vulnerable_only');
104+
}
105+
if (searchTerm) {
106+
searchParams.set('search', searchTerm);
107+
}
108+
let newUrl = window.location.pathname + '?' + searchParams.toString();
109+
if (newUrl.match(/^[^<>'"]*$/)) {
110+
window.location.href = newUrl;
111+
}
112+
});
113+
</script>
114+
{% endblock %}

vulnerabilities/tests/test_view.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,26 @@ def test_package_view_with_valid_purl_without_version(self):
127127
"pkg:nginx/[email protected]",
128128
]
129129

130+
def test_package_search_vulnerable_only_filter(self):
131+
vulnerable_pkg = Package.objects.create(type="npm", name="vulnerable-pkg", version="1.0.0")
132+
non_vulnerable_pkg = Package.objects.create(
133+
type="npm", name="non-vulnerable-pkg", version="2.0.0"
134+
)
135+
vuln = Vulnerability.objects.create(
136+
vulnerability_id="VCID-123", summary="test vulnerability"
137+
)
138+
AffectedByPackageRelatedVulnerability.objects.create(
139+
package=vulnerable_pkg, vulnerability=vuln
140+
)
141+
self.assertTrue(
142+
AffectedByPackageRelatedVulnerability.objects.filter(package=vulnerable_pkg).exists()
143+
)
144+
self.assertFalse(
145+
AffectedByPackageRelatedVulnerability.objects.filter(
146+
package=non_vulnerable_pkg
147+
).exists()
148+
)
149+
130150
def test_package_view_with_valid_purl_and_incomplete_version(self):
131151
qs = PackageSearch().get_queryset(query="pkg:nginx/nginx@1")
132152
pkgs = list(qs)

vulnerabilities/views.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,19 @@ def get_queryset(self, query=None):
5858
on exact purl, partial purl or just name and namespace.
5959
"""
6060
query = query or self.request.GET.get("search") or ""
61-
return (
61+
queryset = (
6262
self.model.objects.search(query)
6363
.with_vulnerability_counts()
6464
.prefetch_related()
6565
.order_by("package_url")
6666
)
67+
if hasattr(self, "request"):
68+
vulnerable_only = self.request.GET.get("vulnerable_only", "").lower()
69+
if vulnerable_only in ["true", "false"]:
70+
queryset = queryset.with_is_vulnerable()
71+
queryset = queryset.filter(is_vulnerable=vulnerable_only == "true")
72+
73+
return queryset
6774

6875

6976
class VulnerabilitySearch(ListView):

0 commit comments

Comments
 (0)