Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for VEX #72

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions component_catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,9 @@ def get_export_spdx_url(self):
def get_export_cyclonedx_url(self):
return self.get_url("export_cyclonedx")

def get_export_vex_url(self):
return self.get_url("export_vex")

def get_about_files(self):
"""
Return the list of all AboutCode files from all the Packages
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% extends "object_form.html" %}
{% block javascripts %}
{{ block.super }}
{% endblock %}
4 changes: 3 additions & 1 deletion component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
from dejacode_toolkit.scancodeio import ScanCodeIO
from dejacode_toolkit.scancodeio import get_package_download_url
from dejacode_toolkit.scancodeio import get_scan_results_as_file_url
from dejacode_toolkit.vex import create_auto_vex
from dejacode_toolkit.vulnerablecode import VulnerableCode
from dje import tasks
from dje.client_data import add_client_data
Expand Down Expand Up @@ -857,7 +858,6 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities):
vulnerability_fields = self.get_vulnerability_fields(vulnerability, dataspace)
fields.extend(vulnerability_fields)
vulnerabilities_count += 1

return fields, vulnerabilities_count

def get_context_data(self, **kwargs):
Expand Down Expand Up @@ -1452,6 +1452,8 @@ def get_vulnerabilities_tab_fields(self, vulnerabilities):
fields = []
vulnerabilities_count = 0

create_auto_vex(self.object, vulnerabilities)

for entry in vulnerabilities:
unresolved = entry.get("affected_by_vulnerabilities", [])
for vulnerability in unresolved:
Expand Down
239 changes: 239 additions & 0 deletions dejacode_toolkit/tests/test_vex.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# DejaCode is a trademark of nexB Inc.
# SPDX-License-Identifier: AGPL-3.0-only
# See https://github.com/nexB/dejacode for support or download.
# See https://aboutcode.org for more information about AboutCode FOSS projects.
#


import json
import os

from django.contrib.auth import get_user_model
from django.test import TestCase

from cyclonedx.output.json import SchemaVersion1Dot4
from serializable import _SerializableJsonEncoder

from component_catalog.models import Package
from dejacode_toolkit.vex import create_auto_vex
from dejacode_toolkit.vex import get_references_and_rating
from dejacode_toolkit.vex import get_vex_document
from dejacode_toolkit.vex import vulnerability_format_vcio_to_cyclonedx
from dje.models import Dataspace
from dje.tests import create_user
from product_portfolio.models import Product
from product_portfolio.models import ProductPackage
from product_portfolio.models import ProductPackageVEX

User = get_user_model()


class VEXTestCase(TestCase):
def setUp(self):
self.nexb_dataspace = Dataspace.objects.create(name="nexB")
self.nexb_user = User.objects.create_superuser(
"nexb_user", "[email protected]", "t3st", self.nexb_dataspace
)
self.basic_user = create_user("basic_user", self.nexb_dataspace)
self.product1 = Product.objects.create(
name="Product1 With Space", version="1.0", dataspace=self.nexb_dataspace
)
self.package1 = Package.objects.create(filename="package1", dataspace=self.nexb_dataspace)
self.package1.type = "pypi"
self.package1.namespace = ""
self.package1.name = "flask"
self.package1.version = "2.3.2"

self.productpacakge1 = ProductPackage.objects.create(
product=self.product1, package=self.package1, dataspace=self.nexb_dataspace
)
self.vex1 = ProductPackageVEX.objects.create(
dataspace=self.productpacakge1.dataspace,
productpackage=self.productpacakge1,
vulnerability_id="VCID-111c-u9bh-aaac",
responses=["CNF"],
justification="CNP",
detail=(
"Automated dataflow analysis and manual "
"code review indicates that the vulnerable code is not reachable,"
" either directly or indirectly."
),
)
self.vex2 = ProductPackageVEX.objects.create(
dataspace=self.productpacakge1.dataspace,
productpackage=self.productpacakge1,
vulnerability_id="VCID-z6fe-2j8a-aaak",
state="R", # resolved
detail="This version of Product DEF has been fixed.",
)

def test_create_auto_vex1(self):
vulnerabilities = [
{
"affected_by_vulnerabilities": [
{
"url": "http://public.vulnerablecode.io/api/vulnerabilities/121332",
"vulnerability_id": "VCID-111c-u9bh-aaac",
}
]
},
{
"affected_by_vulnerabilities": [
{
"url": "https://public.vulnerablecode.io/api/vulnerabilities/121331",
"vulnerability_id": "VCID-uxf9-7c97-aaaj",
}
]
},
]
assert ProductPackageVEX.objects.count() == 2
create_auto_vex(self.package1, vulnerabilities)
assert ProductPackageVEX.objects.count() == 3

# run create_auto_vex agian and make sure that the databse ignore errors
create_auto_vex(self.package1, vulnerabilities)
assert ProductPackageVEX.objects.count() == 3

def test_create_auto_vex2(self):
# duplicated vulnerability
vulnerabilities = [
{
"affected_by_vulnerabilities": [
{
"url": "http://public.vulnerablecode.io/api/vulnerabilities/121332",
"vulnerability_id": "VCID-111c-u9bh-aaac",
}
]
},
{
"affected_by_vulnerabilities": [
{
"url": "http://public.vulnerablecode.io/api/vulnerabilities/121332",
"vulnerability_id": "VCID-111c-u9bh-aaac",
}
]
},
]
assert ProductPackageVEX.objects.count() == 2
create_auto_vex(self.package1, vulnerabilities)
assert ProductPackageVEX.objects.count() == 2

def test_get_references_and_rating(self):
references = [
{
"reference_url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136",
"reference_id": "CVE-2017-1000136",
"scores": [
{
"value": "5.0",
"scoring_system": "cvssv2",
"scoring_elements": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
},
{
"value": "5.3",
"scoring_system": "cvssv3",
"scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
},
],
"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136",
}
]
ref, rate = get_references_and_rating(references)

assert json.dumps(
ref,
cls=_SerializableJsonEncoder,
view_=SchemaVersion1Dot4,
) == json.dumps(
[
{
"id": "CVE-2017-1000136",
"source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"},
}
]
)

assert json.dumps(
rate,
cls=_SerializableJsonEncoder,
view_=SchemaVersion1Dot4,
) == json.dumps(
[
{
"method": "CVSSv2",
"score": "5.0",
"source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"},
"vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
},
{
"method": "CVSSv3",
"score": "5.3",
"source": {"url": "https://nvd.nist.gov/vuln/detail/CVE-2017-1000136"},
"vector": "AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
},
]
)

def test_vulnerability_format_vcic_to_cyclonedx1(self):
vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json")
with open(vul_data_path) as f:
vcio_vulnerability = json.load(f)

vulnerability = vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, self.vex1)

cyclonedx_vul_data_path = os.path.join(
os.path.dirname(__file__), "testfiles", "cyclonedx_vul1.json"
)
with open(cyclonedx_vul_data_path) as f:
cyclonedx_vul = json.load(f)

assert json.dumps(
vulnerability,
cls=_SerializableJsonEncoder,
view_=SchemaVersion1Dot4,
) == json.dumps(cyclonedx_vul)

def test_vulnerability_format_vcic_to_cyclonedx2(self):
vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul2.json")
with open(vul_data_path) as f:
vcio_vulnerability = json.load(f)

vulnerability = vulnerability_format_vcio_to_cyclonedx(vcio_vulnerability, self.vex1)

cyclonedx_vul_data_path = os.path.join(
os.path.dirname(__file__), "testfiles", "cyclonedx_vul2.json"
)
with open(cyclonedx_vul_data_path) as f:
cyclonedx_vul = json.load(f)

assert json.dumps(
vulnerability,
cls=_SerializableJsonEncoder,
view_=SchemaVersion1Dot4,
) == json.dumps(cyclonedx_vul)

def test_get_vex_document1(self):
vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul1.json")
with open(vul_data_path) as f:
vcio_vulnerability = json.load(f)

vex_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vex1.json")
with open(vex_data_path) as f:
vex_data = json.load(f)

assert get_vex_document([vcio_vulnerability], [self.vex1]) == json.dumps(vex_data)

def test_get_vex_document2(self):
vul_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vcio_vul2.json")
with open(vul_data_path) as f:
vcio_vulnerability = json.load(f)

vex_data_path = os.path.join(os.path.dirname(__file__), "testfiles", "vex2.json")
with open(vex_data_path) as f:
vex_data = json.load(f)

assert get_vex_document(
[vcio_vulnerability], [self.vex2], spec_version="1.5"
) == json.dumps(vex_data)
Loading