From 6c4efaa0c975b453f92599a76dd36cd5687899ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 15:46:15 +0000 Subject: [PATCH 1/9] feat: extend search to look into the obj.dependencies and adds eol link --- surface/sca/admin.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/surface/sca/admin.py b/surface/sca/admin.py index 3cf580e4..4d2e2de5 100644 --- a/surface/sca/admin.py +++ b/surface/sca/admin.py @@ -75,11 +75,16 @@ class EndOfLifeDependencyAdmin(DefaultModelAdmin, DefaultFilterMixin, EndOfLifeD "no_support", "is_discontinued", "is_lts", - "link", + "get_link", ] list_filter = ["product", EndOfLifeDependencyBoolFilter, LTSFilter, DiscontinuedFilter, SupportFilter] search_fields = ["product"] + @admin.display(description="Link") + def get_link(self, obj): + if obj.link: + return format_html(f'{obj.link}') + class SCADependencyForm(forms.ModelForm): class Meta: @@ -250,6 +255,17 @@ class SCAProjectAdmin(DefaultModelAdmin): ] search_fields = ["name", "purl", "depends_on__name", "depends_on__purl", "git_source__repo_url"] + def get_search_results(self, request, queryset, search_term): + prop_ids = [] + if search_term: + term = search_term.lower() + prop_ids = [obj.pk for obj in queryset.only("pk") if any(term in dep.lower() for dep in obj.dependencies)] + queryset, use_distinct = super().get_search_results(request, queryset, search_term) + if prop_ids: + queryset |= queryset.model.objects.filter(pk__in=prop_ids) + + return queryset, use_distinct + def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} self.change_form_template = "views/dependencies.html" From d016b0b459cb90c9390d00eeba8339b6856608d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 15:51:57 +0000 Subject: [PATCH 2/9] return --- surface/sca/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/surface/sca/admin.py b/surface/sca/admin.py index 4d2e2de5..2eae2eec 100644 --- a/surface/sca/admin.py +++ b/surface/sca/admin.py @@ -84,6 +84,7 @@ class EndOfLifeDependencyAdmin(DefaultModelAdmin, DefaultFilterMixin, EndOfLifeD def get_link(self, obj): if obj.link: return format_html(f'{obj.link}') + return "" class SCADependencyForm(forms.ModelForm): From a69ec3ae49b2ae696d72720114dbd3006e0b4b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 16:50:03 +0000 Subject: [PATCH 3/9] feat: dependencies search improvements --- surface/sca/admin.py | 26 ++++++++++--------- .../0004_scadependency_dependencies_list.py | 18 +++++++++++++ surface/sca/models.py | 7 +++++ 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 surface/sca/migrations/0004_scadependency_dependencies_list.py diff --git a/surface/sca/admin.py b/surface/sca/admin.py index 2eae2eec..b37d2ef7 100644 --- a/surface/sca/admin.py +++ b/surface/sca/admin.py @@ -254,18 +254,16 @@ class SCAProjectAdmin(DefaultModelAdmin): "git_source", ("git_source__apps", RelatedFieldAjaxListFilter), ] - search_fields = ["name", "purl", "depends_on__name", "depends_on__purl", "git_source__repo_url"] - - def get_search_results(self, request, queryset, search_term): - prop_ids = [] - if search_term: - term = search_term.lower() - prop_ids = [obj.pk for obj in queryset.only("pk") if any(term in dep.lower() for dep in obj.dependencies)] - queryset, use_distinct = super().get_search_results(request, queryset, search_term) - if prop_ids: - queryset |= queryset.model.objects.filter(pk__in=prop_ids) + search_fields = [ + "name", + "purl", + "depends_on__name", + "depends_on__purl", + "git_source__repo_url", + "dependencies_list", + ] - return queryset, use_distinct + readonly_fields = [] def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} @@ -293,7 +291,11 @@ def change_view(self, request, object_id, form_url="", extra_context=None): vulnerabilities = self.get_vulnerabilities(obj) # set fixed_in as True by default if not passed in the request - if "fixed_in" not in request.GET: + if ( + "fixed_in" not in request.GET + and int(request.GET.get("finding_type", models.SCAFinding.FindingType.VULN)) + != models.SCAFinding.FindingType.EOL + ): request.GET = request.GET.copy() request.GET["fixed_in"] = "true" extra_context["vulns_filter"] = SCAFindingFilter(request.GET, queryset=vulnerabilities) diff --git a/surface/sca/migrations/0004_scadependency_dependencies_list.py b/surface/sca/migrations/0004_scadependency_dependencies_list.py new file mode 100644 index 00000000..41fd7a2c --- /dev/null +++ b/surface/sca/migrations/0004_scadependency_dependencies_list.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.8 on 2025-11-11 16:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sca', '0003_scadependency_sbom_uuid_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='scadependency', + name='dependencies_list', + field=models.JSONField(default=list), + ), + ] diff --git a/surface/sca/models.py b/surface/sca/models.py index bc24d8ab..596b0056 100644 --- a/surface/sca/models.py +++ b/surface/sca/models.py @@ -68,6 +68,7 @@ class SCADependency(models.Model): updated_at = models.DateTimeField(auto_now=True) last_scan = models.DateTimeField() sbom_uuid = models.CharField(max_length=255, default=None, null=True) + dependencies_list = models.JSONField(default=list) @staticmethod def get_dependencies_recursively( @@ -207,6 +208,12 @@ def update_vulnerability_counters(self) -> "SCAFindingCounter": def dependencies(self) -> list: return SCADependency.get_dependencies(self) + def save(self, *args, **kwargs): + if hasattr(self, "dependencies"): + if self.is_project and self.is_project: + self.dependencies_list = self.dependencies + super().save(*args, **kwargs) + def __str__(self) -> str: return self.purl From ac170f522f4cb359316dbbd352bcbd5f71cf15e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 18:22:48 +0000 Subject: [PATCH 4/9] fix: post_save --- surface/sca/models.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/surface/sca/models.py b/surface/sca/models.py index 1bdb49a2..b90a674b 100644 --- a/surface/sca/models.py +++ b/surface/sca/models.py @@ -210,12 +210,6 @@ def update_vulnerability_counters(self) -> "SCAFindingCounter": def dependencies(self) -> list: return SCADependency.get_dependencies(self) - def save(self, *args, **kwargs): - if hasattr(self, "dependencies"): - if self.is_project and self.is_project: - self.dependencies_list = self.dependencies - super().save(*args, **kwargs) - def __str__(self) -> str: return self.purl @@ -298,6 +292,13 @@ class Meta: unique_together = ["dependency", "vuln_id"] +@receiver(post_save, sender=SCADependency) +def sca_project_post_save(sender, instance, **kwargs): + deps = instance.dependencies + if instance.is_project: + sender.objects.filter(pk=instance.pk).update(dependencies_list=deps) + + @receiver([post_save, post_delete], sender=SuppressedSCAFinding) def suppressed_sca_finding_post_save_delete(sender, instance, **kwargs): if instance.sca_project: From 1552e9638f5647c1401ec3f1a6f27876f9931122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 18:49:23 +0000 Subject: [PATCH 5/9] fix: dependencies list --- surface/sca/management/commands/resync_sbom_repo.py | 2 ++ surface/sca/management/commands/resync_vulns_counters.py | 4 +++- surface/sca/models.py | 7 ------- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/surface/sca/management/commands/resync_sbom_repo.py b/surface/sca/management/commands/resync_sbom_repo.py index 7a9f7f7b..5853b7c3 100644 --- a/surface/sca/management/commands/resync_sbom_repo.py +++ b/surface/sca/management/commands/resync_sbom_repo.py @@ -238,6 +238,8 @@ def handle_sbom(self, sbom: str) -> bool: if project: project.update_vulnerability_counters() + project.dependencies_list = project.dependencies + project.save() self.processed += 1 return True diff --git a/surface/sca/management/commands/resync_vulns_counters.py b/surface/sca/management/commands/resync_vulns_counters.py index 1d74905c..29a64252 100644 --- a/surface/sca/management/commands/resync_vulns_counters.py +++ b/surface/sca/management/commands/resync_vulns_counters.py @@ -6,7 +6,7 @@ class Command(LogBaseCommand): - help = "Re-sync SCA Projects Vulnerabilities Counters" + help = "Re-sync SCA Projects Vulnerabilities Counters and dependencies list." processed_projects = 0 @@ -14,6 +14,8 @@ def handle(self, *args, **options): self.sync_time = timezone.now() for project in tqdm.tqdm(SCADependency.objects.filter(is_project=True)): project.update_vulnerability_counters() + project.dependencies_list = project.dependencies + project.save() self.processed_projects += 1 SCAFindingCounter.objects.filter(last_sync__lt=self.sync_time).update( diff --git a/surface/sca/models.py b/surface/sca/models.py index b90a674b..b1140fea 100644 --- a/surface/sca/models.py +++ b/surface/sca/models.py @@ -292,13 +292,6 @@ class Meta: unique_together = ["dependency", "vuln_id"] -@receiver(post_save, sender=SCADependency) -def sca_project_post_save(sender, instance, **kwargs): - deps = instance.dependencies - if instance.is_project: - sender.objects.filter(pk=instance.pk).update(dependencies_list=deps) - - @receiver([post_save, post_delete], sender=SuppressedSCAFinding) def suppressed_sca_finding_post_save_delete(sender, instance, **kwargs): if instance.sca_project: From 7929d0ab7115950a4ca05e92d822f3b328df346d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 18:54:46 +0000 Subject: [PATCH 6/9] fix: dep.id --- surface/sca/templates/views/dependencies.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surface/sca/templates/views/dependencies.html b/surface/sca/templates/views/dependencies.html index 1fb34b75..817f535f 100644 --- a/surface/sca/templates/views/dependencies.html +++ b/surface/sca/templates/views/dependencies.html @@ -110,7 +110,7 @@
{% csrf_token %} - + From be1c62601b4a511b5646e7985899f96790ea3a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 23:29:44 +0000 Subject: [PATCH 7/9] feat: download icon --- surface/sca/admin.py | 2 +- surface/surface/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/surface/sca/admin.py b/surface/sca/admin.py index b37d2ef7..62edb5ac 100644 --- a/surface/sca/admin.py +++ b/surface/sca/admin.py @@ -391,7 +391,7 @@ def get_git_source(self, obj): def get_sbom_link(self, obj): if obj.sbom_uuid: return format_html( - 'Download sbom json', + 'download', reverse("sca:download_sbom_as_json", args=[obj.sbom_uuid, obj.name]), ) diff --git a/surface/surface/settings.py b/surface/surface/settings.py index 04c82916..ad5de29d 100644 --- a/surface/surface/settings.py +++ b/surface/surface/settings.py @@ -62,7 +62,7 @@ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", + # "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", From b3acd9a1b62fcc879fd615ee5654f3b4afc84785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= Date: Tue, 11 Nov 2025 23:30:27 +0000 Subject: [PATCH 8/9] fix --- surface/surface/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surface/surface/settings.py b/surface/surface/settings.py index ad5de29d..04c82916 100644 --- a/surface/surface/settings.py +++ b/surface/surface/settings.py @@ -62,7 +62,7 @@ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", - # "django.middleware.csrf.CsrfViewMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", From b249830b44e941bb1a84f2d344516b8eba90583d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Pinto?= <67961647+fpintoppb@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:38:04 +0000 Subject: [PATCH 9/9] Update surface/sca/management/commands/resync_vulns_counters.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Duarte Duarte Signed-off-by: Fábio Pinto <67961647+fpintoppb@users.noreply.github.com> --- surface/sca/management/commands/resync_vulns_counters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/surface/sca/management/commands/resync_vulns_counters.py b/surface/sca/management/commands/resync_vulns_counters.py index 29a64252..dce9ad0c 100644 --- a/surface/sca/management/commands/resync_vulns_counters.py +++ b/surface/sca/management/commands/resync_vulns_counters.py @@ -15,7 +15,7 @@ def handle(self, *args, **options): for project in tqdm.tqdm(SCADependency.objects.filter(is_project=True)): project.update_vulnerability_counters() project.dependencies_list = project.dependencies - project.save() + project.save(update_fields=["dependencies_list"]) self.processed_projects += 1 SCAFindingCounter.objects.filter(last_sync__lt=self.sync_time).update(