Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions vulnerabilities/improvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from vulnerabilities.pipelines import flag_ghost_packages
from vulnerabilities.pipelines import populate_vulnerability_summary_pipeline
from vulnerabilities.pipelines import remove_duplicate_advisories
from vulnerabilities.pipelines.v2_improvers import compute_advisory_todo as compute_advisory_todo_v2
from vulnerabilities.pipelines.v2_improvers import compute_package_risk as compute_package_risk_v2
from vulnerabilities.pipelines.v2_improvers import (
computer_package_version_rank as compute_version_rank_v2,
Expand Down Expand Up @@ -65,6 +66,7 @@
enhance_with_metasploit_v2.MetasploitImproverPipeline,
compute_package_risk_v2.ComputePackageRiskPipeline,
compute_version_rank_v2.ComputeVersionRankPipeline,
compute_advisory_todo_v2.ComputeToDo,
compute_advisory_todo.ComputeToDo,
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Generated by Django 4.2.22 on 2025-07-24 12:05

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("vulnerabilities", "0100_remove_advisoryv2_affecting_packages_and_more"),
]

operations = [
migrations.CreateModel(
name="AdvisoryToDoV2",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"related_advisories_id",
models.CharField(
help_text="SHA1 digest of the unique_content_id field of the applicable advisories.",
max_length=40,
),
),
(
"issue_type",
models.CharField(
choices=[
("MISSING_AFFECTED_PACKAGE", "Advisory is missing affected package"),
("MISSING_FIXED_BY_PACKAGE", "Advisory is missing fixed-by package"),
(
"MISSING_AFFECTED_AND_FIXED_BY_PACKAGES",
"Advisory is missing both affected and fixed-by packages",
),
("MISSING_SUMMARY", "Advisory is missing summary"),
(
"CONFLICTING_FIXED_BY_PACKAGES",
"Advisories have conflicting fixed-by packages",
),
(
"CONFLICTING_AFFECTED_PACKAGES",
"Advisories have conflicting affected packages",
),
(
"CONFLICTING_AFFECTED_AND_FIXED_BY_PACKAGES",
"Advisories have conflicting affected and fixed-by packages",
),
(
"CONFLICTING_SEVERITY_SCORES",
"Advisories have conflicting severity scores",
),
],
db_index=True,
help_text="Select the issue that needs to be addressed from the available options.",
max_length=50,
),
),
(
"issue_detail",
models.TextField(blank=True, help_text="Additional details about the issue."),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True,
help_text="Timestamp indicating when this TODO was created.",
),
),
(
"is_resolved",
models.BooleanField(
db_index=True, default=False, help_text="This TODO is resolved or not."
),
),
(
"resolved_at",
models.DateTimeField(
blank=True,
help_text="Timestamp indicating when this TODO was resolved.",
null=True,
),
),
(
"resolution_detail",
models.TextField(
blank=True, help_text="Additional detail on how this TODO was resolved."
),
),
],
),
migrations.CreateModel(
name="ToDoRelatedAdvisoryV2",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"advisory",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="vulnerabilities.advisoryv2"
),
),
(
"todo",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="vulnerabilities.advisorytodov2",
),
),
],
options={
"unique_together": {("todo", "advisory")},
},
),
migrations.AddField(
model_name="advisorytodov2",
name="advisories",
field=models.ManyToManyField(
help_text="Advisory/ies where this TODO is applicable.",
related_name="advisory_todos",
through="vulnerabilities.ToDoRelatedAdvisoryV2",
to="vulnerabilities.advisoryv2",
),
),
migrations.AlterUniqueTogether(
name="advisorytodov2",
unique_together={("related_advisories_id", "issue_type")},
),
]
71 changes: 71 additions & 0 deletions vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2493,6 +2493,62 @@ class Meta:
unique_together = ("related_advisories_id", "issue_type")


class AdvisoryToDoV2(models.Model):
"""Track the TODOs for advisory/ies that need to be addressed."""

# Since we can not make advisories field (M2M field) unique
# (see https://code.djangoproject.com/ticket/702), we use related_advisories_id
# to avoid creating duplicate issue for same set of advisories,
related_advisories_id = models.CharField(
max_length=40,
help_text="SHA1 digest of the unique_content_id field of the applicable advisories.",
)

advisories = models.ManyToManyField(
"AdvisoryV2",
through="ToDoRelatedAdvisoryV2",
related_name="advisory_todos",
help_text="Advisory/ies where this TODO is applicable.",
)

issue_type = models.CharField(
max_length=50,
choices=ISSUE_TYPE_CHOICES,
db_index=True,
help_text="Select the issue that needs to be addressed from the available options.",
)

issue_detail = models.TextField(
blank=True,
help_text="Additional details about the issue.",
)

created_at = models.DateTimeField(
auto_now_add=True,
help_text="Timestamp indicating when this TODO was created.",
)

is_resolved = models.BooleanField(
default=False,
db_index=True,
help_text="This TODO is resolved or not.",
)

resolved_at = models.DateTimeField(
null=True,
blank=True,
help_text="Timestamp indicating when this TODO was resolved.",
)

resolution_detail = models.TextField(
blank=True,
help_text="Additional detail on how this TODO was resolved.",
)

class Meta:
unique_together = ("related_advisories_id", "issue_type")


class AdvisorySeverity(models.Model):
url = models.URLField(
max_length=1024,
Expand Down Expand Up @@ -2933,6 +2989,21 @@ class Meta:
unique_together = ("todo", "advisory")


class ToDoRelatedAdvisoryV2(models.Model):
todo = models.ForeignKey(
AdvisoryToDoV2,
on_delete=models.CASCADE,
)

advisory = models.ForeignKey(
AdvisoryV2,
on_delete=models.CASCADE,
)

class Meta:
unique_together = ("todo", "advisory")


class PackageQuerySetV2(BaseQuerySet, PackageURLQuerySet):
def search(self, query: str = None):
"""
Expand Down
Loading