Skip to content

Commit

Permalink
Add new Vulnerability list #95
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Aug 23, 2024
1 parent 636d63b commit 1deee0e
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 19 deletions.
25 changes: 25 additions & 0 deletions component_catalog/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
from component_catalog.models import Component
from component_catalog.models import ComponentKeyword
from component_catalog.models import Package
from component_catalog.models import Vulnerability
from component_catalog.programming_languages import PROGRAMMING_LANGUAGES
from dje.filters import DataspacedFilterSet
from dje.filters import DefaultOrderingFilter
from dje.filters import HasRelationFilter
from dje.filters import MatchOrderedSearchFilter
from dje.filters import RelatedLookupListFilter
from dje.filters import SearchFilter
from dje.widgets import BootstrapSelectMultipleWidget
from dje.widgets import DropDownRightWidget
from dje.widgets import SortDropDownWidget
Expand Down Expand Up @@ -272,3 +274,26 @@ def show_created_date(self):
@cached_property
def show_last_modified_date(self):
return not self.sort_value or self.has_sort_by("last_modified_date")


class VulnerabilityFilterSet(DataspacedFilterSet):
q = SearchFilter(
label=_("Search"),
search_fields=["vulnerability_id", "aliases"],
)
sort = DefaultOrderingFilter(
label=_("Sort"),
fields=[
"vulnerability_id",
"affected_packages_count",
"created_date",
"last_modified_date",
],
widget=SortDropDownWidget,
)

class Meta:
model = Vulnerability
fields = [
"q",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<ul class="list-unstyled">
{% for alias in aliases %}
<li>
{% if alias|slice:":3" == "CVE" %}
<a href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% elif alias|slice:":4" == "GHSA" %}
<a href="https://github.com/advisories/{{ alias }}" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% elif alias|slice:":3" == "NPM" %}
<a href="https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% else %}
{{ alias }}
{% endif %}
</li>
{% endfor %}
</ul>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% load i18n %}
{% load inject_preserved_filters from dje_tags %}
{% load urlize_target_blank from dje_tags %}
{% load naturaltime_short from dje_tags %}

<table id="object-list-table" class="table table-bordered table-striped table-md table-fixed-layout text-break vulnerabilities-table">
{% include 'includes/object_list_table_header.html' %}
<tbody>
{% for vulnerability in object_list %}
<tr>
<td>
<strong>
{# TODO #}
<a href="{{ values.vulnerablecode_url }}vulnerabilities/{{ vulnerability.vulnerability_id }}" target="_blank">
{{ vulnerability.vulnerability_id }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
</strong>
</td>
<td>
{% include 'component_catalog/includes/vulnerability_aliases.html' with aliases=vulnerability.aliases only %}
</td>
<td>
{{ vulnerability.affected_packages_count }}
</td>
<td>
{{ vulnerability.fixed_packages_length }}
</td>
</tr>
{% empty %}
<tr><td colspan="10">No results.</td></tr>
{% endfor %}
</tbody>
</table>
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,7 @@
{{ vulnerability.summary }}
</td>
<td>
{% for alias in vulnerability.aliases %}
{% if alias|slice:":3" == "CVE" %}
<a href="https://nvd.nist.gov/vuln/detail/{{ alias }}" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% elif alias|slice:":4" == "GHSA" %}
<a href="https://github.com/advisories/{{ alias }}" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% elif alias|slice:":3" == "NPM" %}
<a href="https://github.com/nodejs/security-wg/blob/main/vuln/npm/{{ alias|slice:"4:" }}.json" target="_blank">{{ alias }}
<i class="fa-solid fa-up-right-from-square mini"></i>
</a>
{% else %}
{{ alias }}
{% endif %}
<br>
{% endfor %}
{% include 'component_catalog/includes/vulnerability_aliases.html' with aliases=vulnerability.aliases only %}
</td>
<td>
{% if vulnerability.fixed_packages_html %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends 'object_list_base.html' %}
{% load i18n %}

{% block page_title %}{% trans "Vulnerabilities" %}{% endblock %}

{% block nav-list-head %}
{{ block.super }}
<li class="nav-item mt-1 ms-3">
<form class="form-search">
<div class="input-group input-group-sm">
<input type="text" class="form-control" name="q" value="{{ search_query|default_if_none:'' }}" aria-label="Search">
<button class="btn btn-outline-dark" type="submit">Search</button>
</div>
</form>
</li>
{% endblock %}
11 changes: 10 additions & 1 deletion component_catalog/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from component_catalog.views import PackageTabScanView
from component_catalog.views import PackageUpdateView
from component_catalog.views import ScanListView
from component_catalog.views import VulnerabilityListView
from component_catalog.views import component_create_ajax_view
from component_catalog.views import delete_scan_view
from component_catalog.views import package_create_ajax_view
Expand Down Expand Up @@ -148,6 +149,14 @@
),
]

vulnerabilities_patterns = [
path(
"vulnerabilities/",
VulnerabilityListView.as_view(),
name="vulnerability_list",
),
]


# WARNING: we moved the components/ patterns from the include to the following
# since we need the packages/ and scans/ to be register on the root "/"
Expand Down Expand Up @@ -239,4 +248,4 @@ def component_path(path_segment, view):
]


urlpatterns = packages_patterns + component_patterns + scans_patterns
urlpatterns = packages_patterns + component_patterns + scans_patterns + vulnerabilities_patterns
47 changes: 47 additions & 0 deletions component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@

from component_catalog.filters import ComponentFilterSet
from component_catalog.filters import PackageFilterSet
from component_catalog.filters import VulnerabilityFilterSet
from component_catalog.forms import AddMultipleToComponentForm
from component_catalog.forms import AddToComponentForm
from component_catalog.forms import AddToProductAdminForm
Expand All @@ -71,6 +72,7 @@
from component_catalog.models import Package
from component_catalog.models import PackageAlreadyExistsWarning
from component_catalog.models import Subcomponent
from component_catalog.models import Vulnerability
from dejacode_toolkit.download import DataCollectionException
from dejacode_toolkit.purldb import PurlDB
from dejacode_toolkit.scancodeio import ScanCodeIO
Expand Down Expand Up @@ -2476,3 +2478,48 @@ def get_tab_fields(self):
tab_fields.extend(get_purldb_tab_fields(purldb_entry, user.dataspace))

return {"fields": tab_fields}


class VulnerabilityListView(
LoginRequiredMixin,
DataspacedFilterView,
):
model = Vulnerability
filterset_class = VulnerabilityFilterSet
template_name = "component_catalog/vulnerability_list.html"
template_list_table = "component_catalog/includes/vulnerability_list_table.html"
table_headers = (
Header("vulnerability_id", _("Vulnerability")),
Header("aliases", _("Aliases")),
# Header("score"),
Header("affected_packages_count", "Affected packages", help_text=" "),
Header("fixed_packages_length", "Fixed by packages", help_text=" "),
# Header("affected_product_count", "Affected products"),
)

def get_queryset(self):
from django.db.models import Func
from django.db.models import IntegerField

return (
super()
.get_queryset()
.only(
"uuid",
"vulnerability_id",
"aliases",
"created_date",
"last_modified_date",
"dataspace",
)
.annotate(
affected_packages_count=Count("affected_packages"),
# TODO: Consider computing this on save()
fixed_packages_length=Func(
"fixed_packages", function="jsonb_array_length", output_field=IntegerField()
),
)
.order_by(
"-last_modified_date",
)
)
1 change: 1 addition & 0 deletions dje/templates/includes/navbar_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{% url 'workflow:request_list' as request_list_url %}
{% url 'component_catalog:scan_list' as scan_list_url %}
{% url 'purldb:purldb_list' as purldb_list_url %}
{% url 'component_catalog:vulnerability_list' as vulnerability_list_url %}
{% url 'django_registration_register' as register_url %}
{% url 'api_v2:api-root' as api_root_url %}
{% url 'account_profile' as account_profile_url %}
Expand Down
8 changes: 8 additions & 0 deletions dje/templates/includes/navbar_header_tools_menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
</a>
{% endif %}
{% endif %}
{% if user.dataspace.enable_vulnerablecodedb_access %}
<div class="dropdown-divider"></div>
<div class="dropdown-header">{% trans 'Vulnerabilities' %}</div>
<a class="dropdown-item{% if vulnerability_list_url in request.path %} active{% endif %}" href="{{ vulnerability_list_url }}">
<i class="fas fa-bug"></i>
{% trans 'Vulnerabilities' %}
</a>
{% endif %}
<div class="dropdown-divider"></div>
<div class="dropdown-header">API</div>
<a class="dropdown-item" href="{{ api_root_url }}">
Expand Down

0 comments on commit 1deee0e

Please sign in to comment.