From 3b34e46cca5f70d15c858c55500c70d7918dbe34 Mon Sep 17 00:00:00 2001 From: "John M. Horan" Date: Wed, 9 Aug 2023 18:47:51 -0700 Subject: [PATCH] Improve matching and reporting code and UI #1228 Reference: https://github.com/nexB/vulnerablecode/issues/1228 Signed-off-by: John M. Horan --- vulnerabilities/models.py | 583 ++---------------- .../templates/package_details.html | 208 +++---- vulnerabilities/tests/test_models.py | 45 +- vulnerabilities/views.py | 3 - vulnerablecode/settings.py | 4 +- vulnerablecode/static/css/custom.css | 30 +- 6 files changed, 153 insertions(+), 720 deletions(-) diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 55f2b00e3..494b045f9 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -660,7 +660,8 @@ def get_fixed_packages(self, package): def get_sibling_packages(self, package): """ - Return a queryset of all packages with the same type, namespace, name, subpath and qualifiers of the `package`, whether or not they fix any vulnerability + Return a queryset of all packages with the same type, namespace, name, subpath and + qualifiers as the target package, whether or not the 'sibling packages' fix any vulnerability. """ return Package.objects.filter( name=package.name, @@ -668,462 +669,126 @@ def get_sibling_packages(self, package): type=package.type, qualifiers=package.qualifiers, subpath=package.subpath, - # packagerelatedvulnerability__fix=True, ).distinct() - # def assign_and_compare_univers_versions(self, fixed_pkg): def assign_univers_version(self, fixed_pkg): """ - Identify which univers version applies to the two packages to be compared (self and a fixed package), - evaluate whether the fixed_pkg version is > than the target affected package, and - return True or False. + Identify which univers version applies to a package and return that version for use, e.g., + in sorting a group of sibling packages (same type etc.). """ - - # TODO: Instead of return True or False based on evaluating the incoming fixed_pkg type to a univers version and then checking whether the fixed version is greater than the affected (self) version, we'll just use the incoming type to assign and return a univers version -- as command_name -- to be used in get_closest_fixed_package() below to add all greater-than versions to the later_matching_fixed_packages list -- which in turn will be fed to self.sort_by_version(later_matching_fixed_packages) - - # Many more to be added. + # TODO: Many more to be added. match_type_to_univers_version = { "conan": versions.ConanVersion, "deb": versions.DebianVersion, + # The following throws an error: AttributeError: module 'univers.versions' has no attribute 'GemVersion' + # "gem": versions.GemVersion, "maven": versions.MavenVersion, + "nginx": versions.NginxVersion, + # "npm": "openssl": versions.OpensslVersion, "pypi": versions.PypiVersion, + # from https://github.com/nexB/univers/blob/205da7ecbf7b0f195662373ea710b2b84a877eb0/tests/test_version_comparison.py + # versions.SemverVersion, + # versions.GolangVersion, + # versions.PypiVersion, + # versions.GenericVersion, + # versions.ComposerVersion, + # versions.NginxVersion, + # versions.ArchLinuxVersion, + # versions.DebianVersion, + # versions.RpmVersion, + # versions.MavenVersion, + # versions.NugetVersion, + # versions.GentooVersion, + # versions.OpensslVersion, + # versions.LegacyOpensslVersion, + # versions.AlpineLinuxVersion, } command_name = "" - matched_type_to_version = match_type_to_univers_version.get(fixed_pkg.type) if matched_type_to_version: - print("\t--------------------------") - print("*** matched_type_to_version = {}".format(matched_type_to_version)) command_name = matched_type_to_version - else: - print("\t--------------------------") - print("*** matched_type_to_version = NO MATCH") - # Using "command_name = versions.Version", the test - # assert versions.Version("0.9") < versions.Version("0.10") - # fails! - # command_name = versions.Version - # Use this as a default fallback instead. command_name = versions.SemverVersion - # if command_name(fixed_pkg.version) > command_name(self.version): - # return True - # else: - # return False - - # Instead return command_name for recipient to use as needed for sorting or perhaps other uses return command_name def sort_by_version(self, later_matching_fixed_packages): # Incoming is a list of - - # ALERT: added this to address server error 500 but related error arose: line 908, in get_closest_fixed_package - # HOT: How is this related to the source of the server error (500)? - if len(later_matching_fixed_packages) == 0: - return - - # Replace find_closest_fixed_by_package()? - print("\nlater_matching_fixed_packages = {}".format(later_matching_fixed_packages)) - print("\nlater_matching_fixed_packages[0] = {}".format(later_matching_fixed_packages[0])) - print( - "\ntype(later_matching_fixed_packages[0]) = {}".format( - type(later_matching_fixed_packages[0]) - ) - ) - # NOTE: This gives us the PURL type but instead we want the PURL itself to pass to assign_univers_version(self, fixed_pkg) and get the command_name in return, which we'll then use in the sort process. - print( - "\nlater_matching_fixed_packages[0].type = {}".format( - later_matching_fixed_packages[0].type - ) - ) - print( - "\ntype(later_matching_fixed_packages[0].type) = {}".format( - type(later_matching_fixed_packages[0].type) - ) - ) - - # Incoming is a list -- later_matching_fixed_packages - # We'll use assign_univers_version() above to get the univers version as a command_name. - # But what do we pass to it? The [0] index of the incoming list, i.e., later_matching_fixed_packages[0]? command_name = self.assign_univers_version(later_matching_fixed_packages[0]) - - print("\n>>> command_name = {}\n".format(command_name)) - - # TODO: Maybe we don't need to convert to a PURL, a list of dictionaries etc.?? - print( - "\n+++++++ later_matching_fixed_packages[0].version = {}".format( - later_matching_fixed_packages[0].version - ) - ) - - # sort test_sort_by_version = [] test_sort_by_version = sorted( - # later_matching_fixed_packages, key=lambda x: versions.DebianVersion(x["version"]) later_matching_fixed_packages, - # key=lambda x: versions.MavenVersion(x.version), key=lambda x: command_name(x.version), ) - print("\ntest_sort_by_version = {}\n".format(test_sort_by_version)) - return test_sort_by_version - # convert_to_dict_list = [] - - # sorted_later_matching_fixed_packages = [] - - # # TODO: First, convert to a list of dictionaries. - # for pkg in later_matching_fixed_packages: - # # pkg is a - # print("pkg = {}".format(pkg)) - # print("type(pkg) = {}".format(type(pkg))) - - # # pkg_str is a string - # pkg_str = pkg.package_url - # print("pkg_str = {}".format(pkg_str)) - # print("type(pkg_str) = {}".format(type(pkg_str))) - - # # purl is a - # purl = PackageURL.from_string(pkg_str) - # print("purl = {}".format(purl)) - # print("type(purl) = {}".format(type(purl))) - - # purl_dict = purl.to_dict() - # print("purl_dict = {}".format(purl_dict)) - # print("type(purl_dict) = {}".format(type(purl_dict))) - - # convert_to_dict_list.append(purl.to_dict()) - # print("HELLO\n") - - # print("\nconvert_to_dict_list = {}\n".format(convert_to_dict_list)) - - # return convert_to_dict_list - # ========================================================== - # sorted_later_matching_fixed_packages = sorted( - # later_matching_fixed_packages, key=lambda x: versions.MavenVersion(x["version"]) - # ) - # print( - # "\nsorted_later_matching_fixed_packages = {}\n".format( - # sorted_later_matching_fixed_packages - # ) - # ) - # print("\n".join(map(str, sorted_later_matching_fixed_packages))) - - # return what? - - # ========================================================== - # ========================================================== - - # def find_closest_fixed_by_package(self, later_matching_fixed_packages): - # # Maybe use sort_by_version() above instead? - # # take the incoming list later_matching_fixed_packages, convert to list of dictionaries, sort by version using univers.version.[version class], choose the top i.e., index [0] and convert back to PURL and return that PURL. - # print("\nlater_matching_fixed_packages = {}\n".format(later_matching_fixed_packages)) - - # closest_fixed_by_package = "TBD" - - # return closest_fixed_by_package - - @property - # def get_fixing_packages(self): - def get_closest_fixed_package(self): - """ - This function identifies the closest fixed package version that is greater than the affected package version and - is the same type, namespace, name, qualifiers and subpath as the affected package. - """ - - print("\nself = {}\n".format(self)) - - # This returns all fixed packages that match the target package (type etc.), regardless of fixed vuln. - # fixed_packages = self.get_fixed_packages(package=self) - # This is clearer. - matching_fixed_packages = self.get_fixed_packages(package=self) - - # This returns a list of the vulnerabilities that affect this package (i.e., self). - qs = self.vulnerabilities.filter(packagerelatedvulnerability__fix=False) - - # This takes the list of vulns affecting the current package, retrieves a list of the fixed packages for each vuln, and assigns the result to a custom attribute, `filtered_fixed_packages` (renamed 'matching_fixed_packages'). - # We use this in a for loop below like this -- qs[vuln_count].filtered_fixed_packages (renamed 'matching_fixed_packages') -- where `vuln_count` is used to iterate through the list of vulns that affect the current package (i.e., self). - qs = qs.prefetch_related( - Prefetch( - "packages", - # queryset=fixed_packages, - queryset=matching_fixed_packages, - # to_attr="filtered_fixed_packages", - to_attr="matching_fixed_packages", - ) - ) - - # Ex: qs[0].filtered_fixed_packages gives us the fixed package(s) for the 1st vuln for this affected package (i.e., self). - print("qs = {}\n".format(qs)) - - # ************************************************************************ - - later_matching_fixed_packages = [] - - vuln_count = 0 - for vuln in qs: - print("vuln = {}\n".format(vuln)) - # print( - # "\tqs[vuln_count].filtered_fixed_packages = {}".format( - # qs[vuln_count].filtered_fixed_packages - # ) - # ) - print( - "\tqs[vuln_count].matching_fixed_packages = {}".format( - qs[vuln_count].matching_fixed_packages - ) - ) - print("") - - # Check the Prefetch qs. - # TODO: Do we want to check whether the fixed version has any vulnerabilities of its own? - # for fixed_pkg in qs[vuln_count].filtered_fixed_packages: - for fixed_pkg in qs[vuln_count].matching_fixed_packages: - print("\tfixed_pkg = {}".format(fixed_pkg)) - print("\tfixed_pkg.type = {}".format(fixed_pkg.type)) - print("\tfixed_pkg.version = {}".format(fixed_pkg.version)) - print("\t--------------------------") - print("\tself.type = {}".format(self.type)) - print("\tself.version = {}".format(self.version)) - - # Assign univers version and compare: False = fixed_pkg.version < self.version (affected version). - - # 2023-08-02 Wednesday 16:01:35. atm immediate_fix is True or False. If instead assign_and_compare_univers_versions() returns the univers version, we could get that here and then test with this or similar right here -- enabling use of the univers version function in other places as well, like a sort_by_version function! - # if command_name(fixed_pkg.version) > command_name(self.version): - # return True - # else: - # return False - # ===================================================== - # Replace this with chunk below - # immediate_fix = self.assign_and_compare_univers_versions(fixed_pkg) - # print("\t--------------------------") - # print("\timmediate_fix = {}\n".format(immediate_fix)) - - # if fixed_pkg in fixed_packages and immediate_fix: - # later_matching_fixed_packages.append(fixed_pkg) - # ===================================================== - # command_name = self.assign_and_compare_univers_versions(fixed_pkg) - # renamed - # TODO: Move this up before the for loop -- both for loops if possible -- to reduce calls! - command_name = self.assign_univers_version(fixed_pkg) - print("\nJust requested command_name >>> {}\n".format(command_name)) - # if fixed_pkg in fixed_packages and command_name(fixed_pkg.version) > command_name( - # self.version - # ): - if fixed_pkg in matching_fixed_packages and command_name( - fixed_pkg.version - ) > command_name(self.version): - later_matching_fixed_packages.append(fixed_pkg) - - vuln_count += 1 - - # find_closest_fixed_by_package -- from the list later_matching_fixed_packages - # closest_fixed_by_package = self.find_closest_fixed_by_package(later_matching_fixed_packages) - - # TODO: or instead use this. This will be a list sorted by univers version class, and here all we need is to grab the [0] index from that list for the closest fixed by package! So we'd return a single closest_fixed_package. - # ALERT: The sort query needs to be done separately for each vulnerability because the list of fixed by packages is likely to be different. As is, we return a single sorted list of all fixed by packages for the affected package and then pass just the [0] package -- not what we want to do! - sort_fixed_by_packages_by_version = self.sort_by_version(later_matching_fixed_packages) - print( - "\nsort_fixed_by_packages_by_version = {}\n".format(sort_fixed_by_packages_by_version) - ) - # ALERT: 2023-08-05 Saturday 23:22:08. Address server error 500? - # ALERT: 2023-08-05 Saturday 23:24:50. This actusally fixed the server error (500) and I can now even see the Packafe details page for pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1 !!! - # HOT: I need to trace back the root cause of the server error (500). I suspect it's something like a record with no fixed by packages or something else that is an empty list but which i try to measure, e.g., for a print statement, or possibly for a real if condition.. - if sort_fixed_by_packages_by_version is None: - return - - closest_fixed_package = sort_fixed_by_packages_by_version[0] - - # print("\n!!! later_matching_fixed_packages = {}\n".format(later_matching_fixed_packages)) - # print( - # "\n!!! sort_fixed_by_packages_by_version = {}\n".format( - # sort_fixed_by_packages_by_version - # ) - # ) - # print("\n!!! closest_fixed_package = {}\n".format(closest_fixed_package)) - - # rebuilt_purl_from_dict = PackageURL( - # closest_fixed_package["type"], - # closest_fixed_package["namespace"], - # closest_fixed_package["name"], - # closest_fixed_package["version"], - # closest_fixed_package["qualifiers"], - # closest_fixed_package["subpath"], - # ) - # print("\n!!! rebuilt_purl_from_dict = {}\n".format(rebuilt_purl_from_dict)) - - # return later_matching_fixed_packages - return sort_fixed_by_packages_by_version - # return [closest_fixed_package] - - # return [rebuilt_purl_from_dict] - @property def fixed_package_details(self): """ - This is a test that might develop into a model-based equivalent of the loops etc. I was doing/trying to do in the Jinja2 template. I'm going to add this as a context so we can see it in the template. + Find and report all fixed by packages (and certain related versions) for each vulnerability in + each affected package. Report packages with no vulnerabilities as well. """ - # return "Hello" - - # vcio_dict = { - # [ - # {"VCID-2nyb-8rwu-aaag": "PURL01"}, - # {"VCID-gqhw-ngh8-aaap": "PURL02"}, - # {"some-other-id": "PURL03"}, - # ] - # } - - print("\n==> This is from the test_property_01() property.\n") - - print("\nself = {}\n".format(self)) - - # This returns all fixed packages that match the target package (type etc.), regardless of fixed vuln. - # fixed_packages = self.get_fixed_packages(package=self) - # This is clearer. + # Get all fixed packages that match the target package (type etc.), regardless of fixed vuln. matching_fixed_packages = self.get_fixed_packages(package=self) - # This returns a list of the vulnerabilities that affect this package (i.e., self). + # Get a list of the vulnerabilities that affect this package (i.e., self). qs = self.vulnerabilities.filter(packagerelatedvulnerability__fix=False) - # TODO: Can we get all sibling packages so that we can then determine which have 0 vulnerabilities and of these the closest and maybe the most recent as well? - all_sibling_packages = self.get_sibling_packages(package=self) - print("\nall_sibling_packages = {}\n".format(all_sibling_packages)) - print("\nlen(all_sibling_packages) = {}\n".format(len(all_sibling_packages))) non_vuln_sibs = [] for sib in all_sibling_packages: if sib.is_vulnerable is False: non_vuln_sibs.append(sib) - print("\nnon_vuln_sibs = {}\n".format(non_vuln_sibs)) - print("\nlen(non_vuln_sibs) = {}\n".format(len(non_vuln_sibs))) # Add just the greater-than versions to a new list command_name = self.assign_univers_version(self) - print( - "\nOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO command_name = {}\n".format( - command_name - ) - ) + later_non_vuln_sibs = [] for non_vuln_sib in non_vuln_sibs: if command_name(non_vuln_sib.version) > command_name(self.version): later_non_vuln_sibs.append(non_vuln_sib) - print("\nlater_non_vuln_sibs = {}\n".format(later_non_vuln_sibs)) - print("\nlen(later_non_vuln_sibs) = {}\n".format(len(later_non_vuln_sibs))) - - # This takes the list of vulns affecting the current package, retrieves a list of the fixed packages for each vuln, and assigns the result to a custom attribute, `filtered_fixed_packages` (renamed 'matching_fixed_packages'). - # We use this in a for loop below like this -- qs[vuln_count].filtered_fixed_packages (renamed 'matching_fixed_packages') -- where `vuln_count` is used to iterate through the list of vulns that affect the current package (i.e., self). + # Take the list of vulns affecting the current package, retrieve a list of the fixed packages for each vuln, and assign the result to a custom attribute, 'matching_fixed_packages'. Ex: qs[0].matching_fixed_packages gives us the fixed package(s) for the 1st vuln for this affected package (i.e., self). qs = qs.prefetch_related( Prefetch( "packages", - # queryset=fixed_packages, queryset=matching_fixed_packages, - # to_attr="filtered_fixed_packages", to_attr="matching_fixed_packages", ) ) - # Ex: qs[0].filtered_fixed_packages gives us the fixed package(s) for the 1st vuln for this affected package (i.e., self). - print("\nzzz qs = {}\n".format(qs)) - purl_dict = {} - purl_dict["purl"] = self.purl - purl_dict.update({"vulnerabilities": []}) - # purl_dict["vulnerabilities"].append({"fruit": "orange"}) + # HOT: In constructing the purl_dict, I need to include packages that have no vulnerabilities -- we still want various dict fields to be populated to reflect the relevant structure and content all such packages. atm only a few fields are included in the dict for such a package. "closest_non_vulnerable_fix", "closest_non_vulnerable_fix_url", "most_recent_non_vulnerable_fix" and "most_recent_non_vulnerable_fix_url" are currently omitted. for vuln in qs: - print("\nzzz vuln = {}\n".format(vuln)) - print("\nzzz type(vuln) = {}\n".format(type(vuln))) - later_matching_fixed_packages = [] - # purl_dict[vuln.vulnerability_id] = "aaa" - # purl_dict.update({"vulnerability": vuln.vulnerability_id}) - purl_dict["vulnerabilities"].append({"vulnerability": vuln.vulnerability_id}) - - # TODO:2023-08-05 Saturday 13:12:28. This returns a list of matching fixed packages for this specific vuln! vuln_matching_fixed_packages = vuln.matching_fixed_packages - print("\nzzz self.purl = {}\n".format(self.purl)) - print("\nzzz vuln = {}\n".format(vuln)) - print("\nzzz vuln_matching_fixed_packages = {}\n".format(vuln_matching_fixed_packages)) - - # TODO: So we need to sort this list by version using the correct univers version and then return the [0] index in that sorted list - # QUESTION: Do we still need to remove lesser-than fixed packages or did we already do that? - - # ============================================================= - # command_name = self.assign_univers_version(fixed_pkg) command_name = self.assign_univers_version(self) - print("\nzzz command_name = {}\n".format(command_name)) - - # ALERT: What if there are no fixed by packages? The following thows an error because the list 'vuln_matching_fixed_packages' is empty! - # [I fixed this, right? ;-] - closest_fixed_package = "" if len(vuln_matching_fixed_packages) > 0: - for fixed_pkg in vuln_matching_fixed_packages: if fixed_pkg in matching_fixed_packages and command_name( fixed_pkg.version ) > command_name(self.version): later_matching_fixed_packages.append(fixed_pkg) - # print("\nJust requested command_name >>> {}\n".format(command_name)) - # # if fixed_pkg in fixed_packages and command_name(fixed_pkg.version) > command_name( - # # self.version - # # ): - # if fixed_pkg in matching_fixed_packages and command_name( - # fixed_pkg.version - # ) > command_name(self.version): - # later_matching_fixed_packages.append(fixed_pkg) - # ============================================================= - # later_matching_fixed_packages = vuln.matching_fixed_packages - - print( - "\nzzz later_matching_fixed_packages = {}\n".format( - later_matching_fixed_packages - ) - ) - sort_fixed_by_packages_by_version = self.sort_by_version( later_matching_fixed_packages ) - print( - "\nzzz sort_fixed_by_packages_by_version = {}\n".format( - sort_fixed_by_packages_by_version - ) - ) + closest_fixed_package = sort_fixed_by_packages_by_version[0] - # closest_fixed_package = sort_fixed_by_packages_by_version[0].purl - # 2023-08-06 Sunday 11:15:03. This returns a queryset of vulns affecting this package. - # HOT: How do we get the closest fixed by package vuln count and list of vulns? I keep getting errors. ;-) closest_fixed_package_vulns = closest_fixed_package.affected_by - # closest_fixed_package_vulns_list = list(closest_fixed_package_vulns) - # ALERT: 2023-08-06 Sunday 14:25:49. This did the trick! - - # closest_fixed_package_vulns_list = [ - # i.vulnerability_id for i in closest_fixed_package_vulns - # ] - - # 2023-08-06 Sunday 16:53:27. Try a named tuple to pass the vuln's vulnerability+id and get_absolute_url. - # FixedPackageVuln = namedtuple("FixedPackageVuln", "vuln_id, vuln_get_absolute_url") - # closest_fixed_package_vulns_list = [ - # FixedPackageVuln( - # vuln_id=fixed_pkg_vuln.vulnerability_id, - # vuln_get_absolute_url=fixed_pkg_vuln.get_absolute_url(), - # ) - # for fixed_pkg_vuln in closest_fixed_package_vulns - # ] - # ALERT: Replace the namedtuple with a dict -- this way it can be added to the purl_dict as a nested dict rather than a list of 2 values. + closest_fixed_package_vulns_dict = [ { "vuln_id": fixed_pkg_vuln.vulnerability_id, @@ -1132,127 +797,25 @@ def fixed_package_details(self): for fixed_pkg_vuln in closest_fixed_package_vulns ] - # === - # closest_fixed_package_vulns_list = closest_fixed_package_vulns.objects.values_list() - - # closest_fixed_package_vulns_list = [] - # for closest_vuln in closest_fixed_package_vulns: - # closest_fixed_package_vulns_list.append(closest_vuln) - # print( - # "\t\nQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ vuln = {}\n".format( - # closest_vuln - # ) - # ) - # print("\t\ntype(closest_vuln) = {}".format(type(closest_vuln))) - - # # closest_fixed_package_vuln_count = len(closest_fixed_package.affected_by) - # print( - # "\t\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type(closest_fixed_package_vulns) = {}\n".format( - # type(closest_fixed_package_vulns) - # ) - # ) - else: closest_fixed_package = "There are no reported fixed packages." - # Is None the value we want? We do not want to display anything but the count = 0. - # closest_fixed_package_vulns = None - # closest_fixed_package_vuln_count = 0 - - # closest_fixed_package_vulns_list = [] - - print("\nzzz closest_fixed_package = {}".format(closest_fixed_package)) - print("zzz type(closest_fixed_package) = {}\n".format(type(closest_fixed_package))) - - # TODO: How do we add 'closest_fixed_by_purl', 'closest_fixed_by_vulns' and 'non_vulnerable_fix'? - - # # for vuln in purl_dict["vulnerabilities"]: - # # # vuln["closest_fixed_by_purl"] = "?????" - # # vuln["closest_fixed_by_purl"] = closest_fixed_package - # # vuln["closest_fixed_by_url"] = "?????" - # # vuln["closest_fixed_by_vulnerabilities"] = "?????" - # # vuln["non_vulnerable_fix"] = "?????" - # # vuln["non_vulnerable_fix_url"] = "?????" for dict_vuln in purl_dict["vulnerabilities"]: - print("\n===================================> vuln = {}\n".format(vuln)) - print("\n===================================> type(vuln) = {}\n".format(type(vuln))) - print("\n===================================> vuln.vcid = {}\n".format(vuln.vcid)) - print( - "\n===================================> dict_vuln['vulnerability'] = {}\n".format( - dict_vuln["vulnerability"] - ) - ) - # TODO: Up above we defined 'non_vuln_sibs' but we still need to remove those with less than version - # ALERT: remove less than versions from 'non_vuln_sibs' - # 2023-08-05 Saturday 20:30:47. Hopefully just wrote the code for that up above, with the new list 'later_non_vuln_sibs'. - closest_non_vulnerable_fix = "" - # if len(non_vuln_sibs) > 0: - # closest_non_vulnerable_fix = self.sort_by_version(non_vuln_sibs)[0] + if len(later_non_vuln_sibs) > 0: closest_non_vulnerable_fix = self.sort_by_version(later_non_vuln_sibs)[0] - # else: - # # closest_non_vulnerable_fix = ( - # # "There are no reported non-vulnerable fixed packages." - # # ) - # closest_non_vulnerable_fix = None most_recent_non_vulnerable_fix = "" if len(later_non_vuln_sibs) > 0: most_recent_non_vulnerable_fix = self.sort_by_version(later_non_vuln_sibs)[-1] else: - # most_recent_non_vulnerable_fix = ( - # "There are no reported non-vulnerable fixed packages." - # ) most_recent_non_vulnerable_fix = None - # if dict_vuln["vulnerability"] == vuln.vulnerability_id: if dict_vuln["vulnerability"] == str(vuln): - # if dict_vuln["vulnerability"] == vuln.vcid: - # dict_vuln["closest_fixed_by_purl"] = "?????" dict_vuln["closest_fixed_by_purl"] = str(closest_fixed_package) dict_vuln["closest_fixed_by_url"] = closest_fixed_package.get_absolute_url() - # dict_vuln["closest_fixed_by_vulnerabilities"] = closest_fixed_package_vuln_count - # dict_vuln["closest_fixed_by_vulnerabilities"] = closest_fixed_package_vulns - # dict_vuln["closest_fixed_by_vulnerabilities"] = ["A", "B"] - - # dict_vuln["closest_fixed_by_vulnerabilities"] = closest_fixed_package_vulns_list - # ALERT: Replace the above list created with a namedtuple with the following dictionary: dict_vuln["closest_fixed_by_vulnerabilities"] = closest_fixed_package_vulns_dict - # ALERT: Moved these up 1 level in the dict. - # dict_vuln["closest_non_vulnerable_fix"] = str(closest_non_vulnerable_fix) - # dict_vuln[ - # "closest_non_vulnerable_fix_url" - # ] = closest_non_vulnerable_fix.get_absolute_url() - # dict_vuln["most_recent_non_vulnerable_fix"] = str( - # most_recent_non_vulnerable_fix - # ) - # dict_vuln[ - # "most_recent_non_vulnerable_fix_url" - # ] = most_recent_non_vulnerable_fix.get_absolute_url() - - # QUESTION: Can we add the non-vuln data as higher-level key-value pairs rather than children of "vulnerabilities"? - - # purl_dict.update({"fruits": []}) - # purl_dict["fruits"].append({"fruit": "apple"}) - # purl_dict["fruits"].append({"fruit": "banana"}) - - # purl_dict.update( - # {"closest_non_vulnerable_fix": str(closest_non_vulnerable_fix)} - # ) - # purl_dict.update( - # { - # "closest_non_vulnerable_fix_url": closest_non_vulnerable_fix.get_absolute_url() - # } - # ) - # purl_dict.update( - # {"most_recent_non_vulnerable_fix": str(most_recent_non_vulnerable_fix)} - # ) - # purl_dict.update( - # { - # "most_recent_non_vulnerable_fix_url": most_recent_non_vulnerable_fix.get_absolute_url() - # } - # ) purl_dict["closest_non_vulnerable_fix"] = str(closest_non_vulnerable_fix) purl_dict[ @@ -1265,76 +828,14 @@ def fixed_package_details(self): "most_recent_non_vulnerable_fix_url" ] = most_recent_non_vulnerable_fix.get_absolute_url() - print("\npurl_dict = {}\n".format(purl_dict)) - - print(json.dumps(purl_dict, indent=4, sort_keys=False)) - - # # Print to text file - pretty_purl_dict = json.dumps(purl_dict, indent=4, sort_keys=False) - # logger = logging.getLogger(__name__) - # logger.setLevel(logging.INFO) - # # logger.addHandler(logging.FileHandler("2023-08-07-pretty_purl_dict.txt")) - # # will this overwrite prior writes? weird output - # logger.addHandler(logging.FileHandler("2023-08-07-pretty_purl_dict.txt", mode="w")) - # logger.info(pretty_purl_dict) - - with open("/home/jmh/pretty_purl_dict.txt", "w") as f: - f.write(pretty_purl_dict) - - alternate_dict_01 = { - "purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1", - "vulnerabilities": [ - { - "vulnerability": "VCID-2nyb-8rwu-aaag", - "closest_fixed_by_purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.2", - # "get_absolute_url": reverse("package_details", args=[self.purl]), - "closest_fixed_by_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.2"], - ), - "closest_fixed_by_vulns": 2, - "non_vulnerable_fix": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1", - "non_vulnerable_fix_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1"], - ), - }, - { - "vulnerability": "VCID-gqhw-ngh8-aaap", - "closest_fixed_by_purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4", - # "get_absolute_url": reverse("package_details", args=[self.purl]), - "closest_fixed_by_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4"], - ), - "closest_fixed_by_vulns": 1, - "non_vulnerable_fix": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1", - "non_vulnerable_fix_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1"], - ), - }, - { - "vulnerability": "VCID-t7e4-g3fr-aaan", - "closest_fixed_by_purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1", - # "get_absolute_url": reverse("package_details", args=[self.purl]), - "closest_fixed_by_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1"], - ), - "closest_fixed_by_vulns": 0, - "non_vulnerable_fix": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1", - "non_vulnerable_fix_url": reverse( - "package_details", - args=["pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.14.0-rc1"], - ), - }, - ], - } - - # return vcio_dict + # Temporary print output during dev/testing: + # print("\npurl_dict = {}\n".format(purl_dict)) + # print(json.dumps(purl_dict, indent=4, sort_keys=False)) - # return alternate_dict_01 + # TODO: Consider whether we want to provide the user with an option to output the dictionary to a file. + # pretty_purl_dict = json.dumps(purl_dict, indent=4, sort_keys=False) + # with open("pretty_purl_dict.txt", "w") as f: + # f.write(pretty_purl_dict) return purl_dict diff --git a/vulnerabilities/templates/package_details.html b/vulnerabilities/templates/package_details.html index dab4283c7..772a2d3f5 100644 --- a/vulnerabilities/templates/package_details.html +++ b/vulnerabilities/templates/package_details.html @@ -40,18 +40,47 @@ + {% if affected_by_vulnerabilities|length != 0 %} + +
+ + + + + + + + + + + +
+ Closest non-vulnerable purl + + {{ fixed_package_details.closest_non_vulnerable_fix }} +
+ Latest non-vulnerable purl + + {{ fixed_package_details.most_recent_non_vulnerable_fix }} +
+
+ + {% endif %} +
Affected by vulnerabilities ({{ affected_by_vulnerabilities|length }})
+ + - + @@ -75,159 +104,63 @@ {% endif %} {% endfor %} - {% empty %} - + {% endfor %} -
Vulnerability Summary AliasesFixed by packagesClosest fixed by packages
- - - - - - - - {% if package.purl in fixed_package_details.purl %} - {% for key, value in fixed_package_details.items %} - {% if key == "vulnerabilities" %} + + {% if package.purl in fixed_package_details.purl %} + {% for key, value in fixed_package_details.items %} + {% if key == "vulnerabilities" %} {% for abc in value %} {% if abc.vulnerability == vulnerability.vulnerability_id %} -
    -
  • - PURL: {{ fixed_package_details.purl }} - - - -
  • -
  • - Vulnerability: {{ abc.vulnerability }} -
  • -
  • - Closest fixed-by PURL: - {{ abc.closest_fixed_by_purl }} -
  • -
  • - Closest fixed-by vulnerability count: {{ abc.closest_fixed_by_vulnerabilities|length }} - {% if abc.closest_fixed_by_vulnerabilities|length != 0 %} - - {% endif %} -
  • -
  • - Closest non-vulnerable fix: - - - {{ fixed_package_details.closest_non_vulnerable_fix }} -
  • -
  • - Most recent non-vulnerable fix: - - - {{ fixed_package_details.most_recent_non_vulnerable_fix }} -
  • -
{% endif %} {% endfor %} - - - {% endif %} - {% endfor %} - {% else %} - NO-- {{ package.purl }} {% endif %} + {% endfor %} - - - - - - + {% endif %}
This package is not known to be affected by vulnerabilities.
@@ -244,7 +177,6 @@ Aliases - {% for vulnerability in fixing_vulnerabilities %} diff --git a/vulnerabilities/tests/test_models.py b/vulnerabilities/tests/test_models.py index 94d1a6fb5..24bf0e166 100644 --- a/vulnerabilities/tests/test_models.py +++ b/vulnerabilities/tests/test_models.py @@ -102,60 +102,43 @@ def test_cwe_not_present_in_weaknesses_db(self): class TestPackageModel(TestCase): def test_univers_version_comparisons(self): assert versions.PypiVersion("1.2.3") < versions.PypiVersion("1.2.4") - assert versions.PypiVersion("0.9") < versions.PypiVersion("0.10") # pkg:deb/debian/jackson-databind@2.12.1-1%2Bdeb11u1 is a real PURL in the DB - # But I get an error when I try to compare 2 PURLs with the same suffix -- - # univers.versions.InvalidVersion: '2.12.1-1%2Bdeb11u1' is not a valid - # Do we need to replace/delete the "%"? - # assert versions.DebianVersion("2.12.1-1%2Bdeb11u1") < versions.DebianVersion( - # "2.13.1-1%2Bdeb11u1" - # ) - # Test the error + # But we need to replace/delete the "%". Test the error: with pytest.raises(versions.InvalidVersion): assert versions.DebianVersion("2.12.1-1%2Bdeb11u1") < versions.DebianVersion( "2.13.1-1%2Bdeb11u1" ) - # Decode the version and test. + # Decode the version and test: assert versions.DebianVersion( urllib.parse.unquote("2.12.1-1%2Bdeb11u1") ) < versions.DebianVersion(urllib.parse.unquote("2.13.1-1%2Bdeb11u1")) + # Expect an error when comparing different types. with pytest.raises(TypeError): assert versions.PypiVersion("0.9") < versions.DebianVersion("0.10") - # Using versions.Version does not correctly make this comparison! + # This demonstrates that versions.Version does not correctly compare 0.9 vs. 0.10. assert not versions.Version("0.9") < versions.Version("0.10") # Use SemverVersion instead as a default fallback version for comparisons. assert versions.SemverVersion("0.9") < versions.SemverVersion("0.10") - def test_assign_and_compare_univers_versions(self): deb01 = models.Package.objects.create(type="deb", name="git", version="2.30.1") deb02 = models.Package.objects.create(type="deb", name="git", version="2.31.1") + assert versions.DebianVersion(deb01.version) < versions.DebianVersion(deb02.version) - immediate_fix01 = deb01.assign_and_compare_univers_versions(deb02) - print("\nimmediate_fix01 = {}\n".format(immediate_fix01)) - # assert deb01.assign_and_compare_univers_versions(deb02) is True - assert deb01.assign_and_compare_univers_versions(deb02) + def test_assign_univers_version(self): + requesting_package = models.Package.objects.create(type="deb", name="git", version="2.30.1") - immediate_fix02 = deb02.assign_and_compare_univers_versions(deb01) - print("\nimmediate_fix02 = {}\n".format(immediate_fix02)) - # assert deb02.assign_and_compare_univers_versions(deb01) is False - assert not deb02.assign_and_compare_univers_versions(deb01) + deb01 = models.Package.objects.create(type="deb", name="git", version="2.31.1") + command_name_deb01 = requesting_package.assign_univers_version(deb01) + assert command_name_deb01 == versions.DebianVersion pypi01 = models.Package.objects.create(type="pypi", name="pyopenssl", version="0.9") - pypi02 = models.Package.objects.create(type="pypi", name="pyopenssl", version="0.10") - - immediate_fix03 = pypi01.assign_and_compare_univers_versions(pypi02) - print("\nimmediate_fix03 = {}\n".format(immediate_fix03)) - # assert pypi01.assign_and_compare_univers_versions(pypi02) is True - assert pypi01.assign_and_compare_univers_versions(pypi02) + command_name_pypi01 = requesting_package.assign_univers_version(pypi01) + assert command_name_pypi01 == versions.PypiVersion gem01 = models.Package.objects.create(type="gem", name="sidekiq", version="0.9") - gem02 = models.Package.objects.create(type="gem", name="sidekiq", version="0.10") - - immediate_fix04 = gem01.assign_and_compare_univers_versions(gem02) - print("\nimmediate_fix04 = {}\n".format(immediate_fix04)) - # assert gem01.assign_and_compare_univers_versions(gem02) is True - assert gem01.assign_and_compare_univers_versions(gem02) + command_name_gem01 = requesting_package.assign_univers_version(gem01) + assert command_name_gem01 == versions.SemverVersion diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index 5247c1c06..7618369e2 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -84,9 +84,6 @@ def get_context_data(self, **kwargs): context["affected_by_vulnerabilities"] = package.affected_by.order_by("vulnerability_id") context["fixing_vulnerabilities"] = package.fixing.order_by("vulnerability_id") context["package_search_form"] = PackageSearchForm(self.request.GET) - # context["get_fixing_packages"] = package.get_fixing_packages - context["get_closest_fixed_package"] = package.get_closest_fixed_package - # context["test_property_01"] = package.test_property_01 context["fixed_package_details"] = package.fixed_package_details return context diff --git a/vulnerablecode/settings.py b/vulnerablecode/settings.py index 122da8025..525127915 100644 --- a/vulnerablecode/settings.py +++ b/vulnerablecode/settings.py @@ -39,9 +39,7 @@ CSRF_TRUSTED_ORIGINS = env.list("CSRF_TRUSTED_ORIGINS", default=[]) # SECURITY WARNING: do not run with debug turned on in production -# DEBUG = env.bool("VULNERABLECODE_DEBUG", default=False) -# DEBUG = "127.0.0.1" -DEBUG = True +DEBUG = env.bool("VULNERABLECODE_DEBUG", default=False) # SECURITY WARNING: do not run with debug turned on in production DEBUG_TOOLBAR = env.bool("VULNERABLECODE_DEBUG_TOOLBAR", default=False) diff --git a/vulnerablecode/static/css/custom.css b/vulnerablecode/static/css/custom.css index 205df5c63..bf094f70c 100644 --- a/vulnerablecode/static/css/custom.css +++ b/vulnerablecode/static/css/custom.css @@ -196,16 +196,14 @@ code { } .two-col-left { - width: 160px; + width: 250px; text-align: right !important; font-weight: bold; - padding-right: 20px !important; + padding-right: 15px !important; line-height: 20px; - border: solid 1px #e8e8e8 !important; } .two-col-right { - border: solid 1px #e8e8e8 !important; line-height: 20px; } @@ -482,3 +480,27 @@ ul.fixed_by_bullet li li li { margin-bottom: 3px; display: block; } + +.non-floating-purl { + position: relative; + width: 100%; + z-index: 100; + margin-bottom: 0px; +} + +.non-floating-purl .table td, +.non-floating-purl .table tbody tr:last-child td, +.non-floating-purl .table th { + border: solid 1px #dbdbdb; + background-color: #ffffff; +} + +.non-vuln { + margin-top: -25px; +} + +.non-vuln .table td, +.non-vuln .table tbody tr:last-child td, +.non-vuln .table th { + border: solid 1px #dbdbdb; +}