Skip to content

Commit 9311e77

Browse files
committed
Store package version release date in DB
1 parent 03643ae commit 9311e77

File tree

4 files changed

+139
-2
lines changed

4 files changed

+139
-2
lines changed

vulnerabilities/improvers/valid_versions.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
from vulnerabilities.improver import Improver
4141
from vulnerabilities.improver import Inference
4242
from vulnerabilities.models import Advisory
43+
from vulnerabilities.models import Package
44+
from vulnerabilities.models import PackageV2
4345
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline
4446
from vulnerabilities.pipelines.github_importer import GitHubAPIImporterPipeline
4547
from vulnerabilities.pipelines.gitlab_importer import GitLabImporterPipeline
@@ -73,15 +75,45 @@ def get_package_versions(
7375
"""
7476
Return a list of versions published before `until` for the `package_url`
7577
"""
76-
versions = package_versions.versions(str(package_url))
78+
versions = list(package_versions.versions(str(package_url)) or [])
79+
self.store_package_release_dates(package_url=package_url, versions=versions)
7780
versions_before_until = []
78-
for version in versions or []:
81+
for version in versions:
7982
if until and version.release_date and version.release_date > until:
8083
continue
8184
versions_before_until.append(version.value)
8285

8386
return versions_before_until
8487

88+
def store_package_release_dates(self, package_url: PackageURL, versions: List) -> None:
89+
"""
90+
Persist release dates for known package versions in both Package and PackageV2.
91+
"""
92+
releases_by_version = {
93+
version.value: version.release_date
94+
for version in versions
95+
if getattr(version, "value", None) and getattr(version, "release_date", None)
96+
}
97+
if not releases_by_version:
98+
return
99+
100+
filters = {
101+
"type": package_url.type,
102+
"namespace": package_url.namespace,
103+
"name": package_url.name,
104+
"version__in": list(releases_by_version),
105+
}
106+
107+
for model in (Package, PackageV2):
108+
packages_to_update = []
109+
for package in model.objects.filter(**filters).only("id", "version", "release_date"):
110+
release_date = releases_by_version.get(package.version)
111+
if release_date and package.release_date != release_date:
112+
package.release_date = release_date
113+
packages_to_update.append(package)
114+
if packages_to_update:
115+
model.objects.bulk_update(packages_to_update, fields=["release_date"])
116+
85117
def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
86118
"""
87119
Yield Inferences for the given advisory data
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
from django.db import migrations
10+
from django.db import models
11+
12+
13+
class Migration(migrations.Migration):
14+
dependencies = [
15+
("vulnerabilities", "0116_advisoryv2_advisory_content_hash"),
16+
]
17+
18+
operations = [
19+
migrations.AddField(
20+
model_name="package",
21+
name="release_date",
22+
field=models.DateTimeField(
23+
blank=True,
24+
db_index=True,
25+
help_text="Date when this package version was released by the upstream package source.",
26+
null=True,
27+
),
28+
),
29+
migrations.AddField(
30+
model_name="packagev2",
31+
name="release_date",
32+
field=models.DateTimeField(
33+
blank=True,
34+
db_index=True,
35+
help_text="Date when this package version was released by the upstream package source.",
36+
null=True,
37+
),
38+
),
39+
]

vulnerabilities/models.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,13 @@ class Package(PackageURLMixin):
896896
db_index=True,
897897
)
898898

899+
release_date = models.DateTimeField(
900+
null=True,
901+
blank=True,
902+
db_index=True,
903+
help_text="Date when this package version was released by the upstream package source.",
904+
)
905+
899906
objects = PackageQuerySet.as_manager()
900907

901908
class Meta:
@@ -3384,6 +3391,13 @@ class PackageV2(PackageURLMixin):
33843391
db_index=True,
33853392
)
33863393

3394+
release_date = models.DateTimeField(
3395+
null=True,
3396+
blank=True,
3397+
db_index=True,
3398+
help_text="Date when this package version was released by the upstream package source.",
3399+
)
3400+
33873401
def __str__(self):
33883402
return self.package_url
33893403

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
from dataclasses import dataclass
11+
from datetime import datetime
12+
from datetime import timezone as dt_timezone
13+
14+
import pytest
15+
from packageurl import PackageURL
16+
17+
from vulnerabilities.improvers.valid_versions import DebianBasicImprover
18+
from vulnerabilities.models import Package
19+
from vulnerabilities.models import PackageV2
20+
21+
22+
@dataclass
23+
class MockVersion:
24+
value: str
25+
release_date: datetime | None
26+
27+
28+
@pytest.mark.django_db
29+
def test_get_package_versions_stores_release_date(monkeypatch):
30+
package = Package.objects.create(type="pypi", name="demo", version="1.0.0")
31+
package_v2 = PackageV2.objects.create(type="pypi", name="demo", version="1.0.0")
32+
33+
release_date = datetime(2024, 1, 15, tzinfo=dt_timezone.utc)
34+
mock_versions = [
35+
MockVersion(value="1.0.0", release_date=release_date),
36+
MockVersion(value="2.0.0", release_date=None),
37+
]
38+
39+
monkeypatch.setattr(
40+
"vulnerabilities.improvers.valid_versions.package_versions.versions",
41+
lambda *_args, **_kwargs: mock_versions,
42+
)
43+
44+
purl = PackageURL(type="pypi", name="demo")
45+
versions = DebianBasicImprover().get_package_versions(package_url=purl)
46+
47+
assert versions == ["1.0.0", "2.0.0"]
48+
49+
package.refresh_from_db()
50+
package_v2.refresh_from_db()
51+
assert package.release_date == release_date
52+
assert package_v2.release_date == release_date

0 commit comments

Comments
 (0)