Skip to content

Commit

Permalink
feat: Enhanced ecosystem support for checking latest versions of pack…
Browse files Browse the repository at this point in the history
…ages
  • Loading branch information
anthonyharrison committed Feb 22, 2024
1 parent d218a1b commit 7606fe5
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 64 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ The following checks are performed on each package item:

- Check that a version is specified.

- Check that the package version is the latest released version of the package. The latest version checks are only performed if the `--offline` option is not specified and is only performed for Python modules available on the [Python Package Index (PyPi)](https://pypi.org/).
- Check that the package version is the latest released version of the package. The latest version checks are only performed if the `--offline` option is not specified.

- Check that a mature version of the package is being used as determined by the value specified in the `--age` option. The release date checks are only performed if the `--offline` option is not specified and is only performed for Python modules available on the [Python Package Index (PyPi)](https://pypi.org/).
- Check that a mature version of the package is being used as determined by the value specified in the `--age` option. The release date checks are only performed if the `--offline` option is not specified.

- Check the age of a package being used, which is not the latest released version, is greater than the value specified in the `--maxage` option.
The check is only performed if the `--offline` option is not specified and is only performed for Python modules available on the [Python Package Index (PyPi)](https://pypi.org/).
The check is only performed if the `--offline` option is not specified.

- Check that a license is specified and that the license identified is a valid [SPDX License identifier](https://spdx.org/licenses/). Note that NOASSERTION is not considered a valid license.

Expand All @@ -172,6 +172,22 @@ The check is only performed if the `--offline` option is not specified and is on

- Check that a [CPE specification](https://nvd.nist.gov/products/cpe) is provided for the package.

### Latest package version checks

The checks for the latest package version are performed for packages within the following language ecosystems:

- dart
- go
- java
- javascript
- .net
- perl
- python
- r
- ruby
- rust
- swift

### Relationships

The following checks are performed:
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
lib4sbom >= 0.5.0
lib4package >= 0.2.0
rich
requests
packageurl-python
Expand Down
77 changes: 19 additions & 58 deletions sbomaudit/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import dateutil.parser
import pytz
import requests
from lib4package.metadata import Metadata
from lib4sbom.data.document import SBOMDocument
from lib4sbom.license import LicenseScanner
from packageurl import PackageURL
Expand All @@ -23,6 +24,7 @@ def __init__(self, options={}, output=""):
self.purl_check = options.get("purlcheck", False)
self.license_check = options.get("license_check", True)
self.age = int(options.get("age", "0"))
self.debug = options.get("debug", False)
DAYS_IN_YEAR = 365
self.maxage = int(options.get("maxage", "2")) * DAYS_IN_YEAR
self.license_scanner = LicenseScanner()
Expand Down Expand Up @@ -85,24 +87,6 @@ def _check_value(self, text, values, data_item):
def _check(self, text, value, failure_text="MISSING"):
self._show_result(text, value, failure_text=failure_text)

def find_latest_version_maven(self, name):
"""Returns the latest version of the package available at Maven Central."""

url: str = f"https://search.maven.org/solrsearch/select?q=a:{name}&wt=json"
maven_version = None
try:
package_json = requests.get(url).json()
maven_version = package_json["response"]["docs"][0]["latestVersion"]
except Exception as error:
print(
f"""
-------------------------- Can't check Maven Central for the latest version -------
Exception details: {error}
Please make sure you have a working internet connection or try again later.
"""
)
return maven_version

def find_latest_version(self, name, version=None):
"""Returns the version and release date of the package available at PyPI."""

Expand All @@ -119,40 +103,16 @@ def find_latest_version(self, name, version=None):
"upload_time_iso_8601"
]
except Exception as error:
print(
f"""
-------------------------- Can't check PyPi for the latest version -------
Exception details: {error}
Please make sure you have a working internet connection or try again later.
"""
)
if self.debug:
print(f"Unable to retrieve Python data for {name} - {version}. {error}")
return pypi_version, pypi_date

def get_latest_version(self, name, backend="PyPI"):
url: str = f"https://release-monitoring.org/api/v2/projects/?name={name}"
latest_version = None
try:
package_json = requests.get(url).json()
if (
package_json["total_items"] == 1
and package_json["items"][0]["backend"].lower() == backend.lower()
):
latest_version = package_json["items"][0]["stable_versions"][0]
else:
for item in package_json["items"]:
if item["backend"].lower() == backend.lower():
latest_version = item["stable_versions"][0]
break

except Exception as error:
print(
f"""
-------------------------- Can't check for the latest version -------
Exception details: {error}
Please make sure you have a working internet connection or try again later.
"""
)
return latest_version
def get_package_info(self, package_name, package_type):
self.package_metadata = Metadata(package_type, debug=self.debug)
self.package_metadata.get_package(package_name)
latest_version = self.package_metadata.get_latest_version()
latest_date = self.package_metadata.get_latest_release_time()
return latest_version, latest_date

def process_file(self, filename, allow):
# Only process if file exists
Expand Down Expand Up @@ -401,18 +361,20 @@ def audit_sbom(self, sbom_parser):
_, latest_date = self.find_latest_version(
name, version=version
)
elif purl["type"] == "maven":
# Maven package detected
latest_version = (
self.find_latest_version_maven(name)
)
else:
latest_version = self.get_latest_version(
name, backend=purl["type"]
(
latest_version,
latest_date,
) = self.get_package_info(
name, purl["type"]
)
purl_name = purl["name"]
except ValueError:
purl_used = False
if self.debug:
print(
f"Version check for {name} within {purl['type']} ecosystem. {latest_version} {latest_date}"
)
elif external_ref[1] in ["cpe22Type", "cpe23Type"]:
cpe_used = True

Expand Down Expand Up @@ -473,7 +435,6 @@ def audit_sbom(self, sbom_parser):
failure_text=report,
)
if latest_date is not None:

release_date = dateutil.parser.parse(latest_date)
release_age = (
pytz.utc.localize(datetime.datetime.utcnow())
Expand Down
2 changes: 1 addition & 1 deletion sbomaudit/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


def main(argv=None):

argv = argv or sys.argv
app_name = "sbomaudit"
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -164,6 +163,7 @@ def main(argv=None):
"license_check": not args["disable_license_check"],
"age": args["age"],
"maxage": args["maxage"],
"debug": args["debug"],
}

sbom_parser = SBOMParser()
Expand Down
4 changes: 2 additions & 2 deletions sbomaudit/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2023 Anthony Harrison
# Copyright (C) 2024 Anthony Harrison
# SPDX-License-Identifier: Apache-2.0

VERSION: str = "0.3.1"
VERSION: str = "0.4.0"

0 comments on commit 7606fe5

Please sign in to comment.