diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa611212..bb2e36b2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,12 @@ Release notes ### Version 5.0.2-dev +- Lookup in PurlDB by purl in Add Package form. + When a Package URL is available in the context of the "Add Package" form, + for example when using a link from the Vulnerabilities tab, + data is fetched from the PurlDB to initialize the form. + https://github.com/nexB/dejacode/issues/47 + ### Version 5.0.1 - Improve the stability of the "Check for new Package versions" feature. diff --git a/component_catalog/tests/test_views.py b/component_catalog/tests/test_views.py index 5608cd5f..a1e71a3b 100644 --- a/component_catalog/tests/test_views.py +++ b/component_catalog/tests/test_views.py @@ -3654,6 +3654,53 @@ def test_component_catalog_package_add_view_create_proper(self): expected = "Package "name.zip" was successfully created." self.assertContains(response, expected) + @mock.patch("dejacode_toolkit.purldb.PurlDB.request_get") + @mock.patch("dejacode_toolkit.purldb.PurlDB.is_configured") + def test_component_catalog_package_add_view_initial_data( + self, mock_is_configured, mock_request_get + ): + self.client.login(username=self.super_user.username, password="secret") + add_url = reverse("component_catalog:package_add") + + mock_is_configured.return_value = True + self.dataspace.enable_purldb_access = True + self.dataspace.save() + + puyrldb_entry = { + "filename": "abbot-1.4.0.jar", + "release_date": "2015-09-22", + "type": "maven", + "namespace": "abbot", + "name": "abbot", + "version": "1.4.0", + "qualifiers": "", + "subpath": "", + "primary_language": "Java", + "description": "Abbot Java GUI Test Library", + "declared_license_expression": "bsd-new OR eps-1.0 OR apache-2.0 OR mit", + } + mock_request_get.return_value = { + "count": 1, + "results": [puyrldb_entry], + } + + response = self.client.get(add_url) + self.assertEqual({}, response.context["form"].initial) + + response = self.client.get(add_url + "?package_url=pkg:maven/abbot/abbot@1.4.0") + expected = { + "filename": "abbot-1.4.0.jar", + "release_date": "2015-09-22", + "type": "maven", + "namespace": "abbot", + "name": "abbot", + "version": "1.4.0", + "primary_language": "Java", + "description": "Abbot Java GUI Test Library", + "license_expression": "bsd-new OR eps-1.0 OR apache-2.0 OR mit", + } + self.assertEqual(expected, response.context["form"].initial) + @mock.patch("dje.tasks.scancodeio_submit_scan.delay") @mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.is_configured") def test_component_catalog_package_add_view_create_with_submit_scan( diff --git a/component_catalog/views.py b/component_catalog/views.py index 578e410a..01258df5 100644 --- a/component_catalog/views.py +++ b/component_catalog/views.py @@ -20,6 +20,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.core import signing +from django.core.validators import EMPTY_VALUES from django.db.models import Prefetch from django.http import FileResponse from django.http import Http404 @@ -45,6 +46,7 @@ from natsort import natsorted from notifications.signals import notify from packageurl import PackageURL +from packageurl.contrib import purl2url from packageurl.contrib import url2purl from component_catalog.filters import ComponentFilterSet @@ -1896,26 +1898,52 @@ def get_initial(self): """Pre-fill the form with initial data from a PurlDB entry or a `package_url`.""" initial = super().get_initial() - purldb_uuid = self.request.GET.get("purldb_uuid", None) - if purldb_uuid: - purldb_entry = PurlDB(self.request.user).get_package(purldb_uuid) + if purldb_entry := self.get_entry_from_purldb(): purldb_entry["license_expression"] = purldb_entry.get("declared_license_expression") - model_fields = [field.name for field in Package._meta.get_fields()] + model_fields = [ + field.name + for field in Package._meta.get_fields() + # Generic keywords are not supported because of validation + if field.name != "keywords" + ] initial_from_purldb_entry = { field_name: value for field_name, value in purldb_entry.items() - if value and field_name in model_fields + if value not in EMPTY_VALUES and field_name in model_fields } initial.update(initial_from_purldb_entry) + messages.info(self.request, "Initial data fetched from PurlDB.") - package_url = self.request.GET.get("package_url", None) - if package_url: + elif package_url := self.request.GET.get("package_url", None): purl = PackageURL.from_string(package_url) package_url_dict = purl.to_dict(encode=True, empty="") initial.update(package_url_dict) + if download_url := purl2url.get_download_url(package_url): + initial.update({"download_url": download_url}) return initial + def get_entry_from_purldb(self): + user = self.request.user + purldb = PurlDB(user) + is_purldb_enabled = all( + [ + purldb.is_configured(), + user.dataspace.enable_purldb_access, + ] + ) + + if not is_purldb_enabled: + return + + purldb_uuid = self.request.GET.get("purldb_uuid", None) + package_url = self.request.GET.get("package_url", None) + + if purldb_uuid: + return purldb.get_package(purldb_uuid) + elif package_url: + return purldb.get_package_by_purl(package_url) + def get_success_message(self, cleaned_data): success_message = super().get_success_message(cleaned_data) diff --git a/dejacode_toolkit/purldb.py b/dejacode_toolkit/purldb.py index 55d0315b..5ddb5f7b 100644 --- a/dejacode_toolkit/purldb.py +++ b/dejacode_toolkit/purldb.py @@ -54,6 +54,11 @@ def get_package(self, uuid): """Get a Package details entry providing its `uuid`.""" return self.request_get(url=f"{self.package_api_url}{uuid}/") + def get_package_by_purl(self, package_url): + """Get a Package details entry providing its `package_url`.""" + if results := self.find_packages({"purl": package_url}): + return results[0] + def find_packages(self, payload, timeout=None): """Get Packages details using provided `payload` filters on the PurlDB package list.""" response = self.request_get(self.package_api_url, params=payload, timeout=timeout) diff --git a/dje/templates/includes/messages_alert.html b/dje/templates/includes/messages_alert.html index e4d8871e..ec78efb7 100644 --- a/dje/templates/includes/messages_alert.html +++ b/dje/templates/includes/messages_alert.html @@ -1,6 +1,6 @@ {% for message in messages %} {% if wrapper_classes %}
{% endif %} -
- {% include 'includes/form_errors_alert.html' %} + {% block messages-alert %} + {% include 'includes/form_errors_alert.html' %} + {% include 'includes/messages_alert.html' with wrapper_classes='container p-0' %} + {% endblock %}