Skip to content

Commit

Permalink
Collect additional vulnerability data, surface in JSON API (pypi#10197)
Browse files Browse the repository at this point in the history
* Collect additional data from vulnerability reports

* Surface vulnerabilities in JSON API

* Address code review

* Add help section

* Update translations
  • Loading branch information
di authored and domdfcoding committed Jun 7, 2022
1 parent 455b5c3 commit b930725
Show file tree
Hide file tree
Showing 10 changed files with 434 additions and 252 deletions.
31 changes: 31 additions & 0 deletions tests/common/db/integrations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import factory

from warehouse.integrations.vulnerabilities.models import VulnerabilityRecord

from .base import WarehouseFactory
from .packaging import ReleaseFactory


class VulnerabilityRecordFactory(WarehouseFactory):
class Meta:
model = VulnerabilityRecord

id = factory.fuzzy.FuzzyText(length=12)
source = factory.fuzzy.FuzzyText(length=12)
link = factory.fuzzy.FuzzyText(length=12)
aliases = factory.Sequence(lambda n: "alias" + str(n))
releases = factory.SubFactory(ReleaseFactory)
details = factory.fuzzy.FuzzyText(length=12)
fixed_in = factory.Sequence(lambda n: str(n) + ".0")
4 changes: 4 additions & 0 deletions tests/unit/integration/vulnerabilities/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def test_vulnerability_report_request_from_api_request():
"id": "vuln_id",
"link": "vulns.com/vuln_id",
"aliases": ["vuln_alias"],
"details": "some details",
"events": [{"introduced": "1.0.0"}, {"fixed": "1.0.1"}, {"fixed": "2.0.0"}],
}
)

Expand All @@ -54,6 +56,8 @@ def test_vulnerability_report_request_from_api_request():
assert request.vulnerability_id == "vuln_id"
assert request.advisory_link == "vulns.com/vuln_id"
assert request.aliases == ["vuln_alias"]
assert request.details == "some details"
assert request.fixed_in == ["1.0.1", "2.0.0"]


def test_invalid_vulnerability_report():
Expand Down
32 changes: 32 additions & 0 deletions tests/unit/legacy/api/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from warehouse.packaging.models import Dependency, DependencyKind

from ....common.db.accounts import UserFactory
from ....common.db.integrations import VulnerabilityRecordFactory
from ....common.db.packaging import (
DescriptionFactory,
FileFactory,
Expand Down Expand Up @@ -432,6 +433,7 @@ def test_detail_renders(self, pyramid_config, db_request, db_session):
}
],
"last_serial": je.id,
"vulnerabilities": [],
}

def test_minimal_renders(self, pyramid_config, db_request):
Expand Down Expand Up @@ -538,8 +540,38 @@ def test_minimal_renders(self, pyramid_config, db_request):
}
],
"last_serial": je.id,
"vulnerabilities": [],
}

def test_vulnerabilities_renders(self, pyramid_config, db_request):
project = ProjectFactory.create(has_docs=False)
release = ReleaseFactory.create(project=project, version="0.1")
VulnerabilityRecordFactory.create(
id="PYSEC-001",
source="the source",
link="the link",
aliases=["alias1", "alias2"],
details="some details",
fixed_in=["3.3.2"],
releases=[release],
)

url = "/the/fake/url/"
db_request.route_url = pretend.call_recorder(lambda *args, **kw: url)

result = json.json_release(release, db_request)

assert result["vulnerabilities"] == [
{
"id": "PYSEC-001",
"source": "the source",
"link": "the link",
"aliases": ["alias1", "alias2"],
"details": "some details",
"fixed_in": ["3.3.2"],
},
]


class TestJSONReleaseSlash:
def test_normalizing_redirects(self, db_request):
Expand Down
11 changes: 11 additions & 0 deletions warehouse/integrations/vulnerabilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ def __init__(
vulnerability_id: str,
advisory_link: str,
aliases: List[str],
details: str,
fixed_in: List[str],
):
self.project = project
self.versions = versions
self.vulnerability_id = vulnerability_id
self.advisory_link = advisory_link
self.aliases = aliases
self.details = details
self.fixed_in = fixed_in

@classmethod
def from_api_request(cls, request):
Expand All @@ -59,6 +63,13 @@ def from_api_request(cls, request):
vulnerability_id=request["id"],
advisory_link=request["link"],
aliases=request["aliases"],
details=request.get("details"),
fixed_in=[
version
for event in request.get("events", [])
for event_type, version in event.items()
if event_type == "fixed"
],
)


Expand Down
6 changes: 6 additions & 0 deletions warehouse/integrations/vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ class VulnerabilityRecord(db.Model):
# e.g. "CVE-2021-12345"
aliases = Column(ARRAY(String))

# Details about the vulnerability
details = Column(String)

# Events of introduced/fixed versions
fixed_in = Column(ARRAY(String))

releases = orm.relationship(
"Release",
back_populates="vulnerabilities",
Expand Down
2 changes: 2 additions & 0 deletions warehouse/integrations/vulnerabilities/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def _analyze_vulnerability(request, vulnerability_report, origin, metrics):
source=origin,
link=report.advisory_link,
aliases=report.aliases,
details=report.details,
fixed_in=report.fixed_in,
)
_add_vuln_record(request, vulnerability_record)

Expand Down
14 changes: 14 additions & 0 deletions warehouse/legacy/api/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ def json_release(release, request):
for r, fs in releases.items()
}

# Serialize a list of vulnerabilties for this release
vulnerabilities = [
{
"id": vulnerability_record.id,
"source": vulnerability_record.source,
"link": vulnerability_record.link,
"aliases": vulnerability_record.aliases,
"details": vulnerability_record.details,
"fixed_in": vulnerability_record.fixed_in,
}
for vulnerability_record in release.vulnerabilities
]

return {
"info": {
"name": project.name,
Expand Down Expand Up @@ -198,6 +211,7 @@ def json_release(release, request):
},
"urls": releases[release.version],
"releases": releases,
"vulnerabilities": vulnerabilities,
"last_serial": project.last_serial,
}

Expand Down
Loading

0 comments on commit b930725

Please sign in to comment.