Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor severity score model and fix incorrect suse scores #1636

Merged
merged 14 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
25 changes: 21 additions & 4 deletions vulnerabilities/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,20 @@ def to_representation(self, instance):


class VulnerabilityReferenceSerializer(serializers.ModelSerializer):
scores = VulnerabilitySeveritySerializer(many=True, source="vulnerabilityseverity_set")
scores = serializers.SerializerMethodField()
reference_url = serializers.CharField(source="url")

class Meta:
model = VulnerabilityReference
fields = ["reference_url", "reference_id", "reference_type", "scores", "url"]

def get_scores(self, instance):
severities_related_to_reference = []
if vulnerability := self.context.get("vulnerability"):
severities_related_to_reference = vulnerability.severities.filter(url=instance.url)

return VulnerabilitySeveritySerializer(severities_related_to_reference, many=True).data


class BaseResourceSerializer(serializers.HyperlinkedModelSerializer):
"""
Expand Down Expand Up @@ -199,8 +206,7 @@ class VulnerabilitySerializer(BaseResourceSerializer):
many=True, source="filtered_fixed_packages", read_only=True
)
affected_packages = MinimalPackageSerializer(many=True, read_only=True)

references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set")
references = serializers.SerializerMethodField()
aliases = AliasSerializer(many=True, source="alias")
exploits = ExploitSerializer(many=True, read_only=True)
weaknesses = WeaknessSerializer(many=True)
Expand All @@ -214,10 +220,21 @@ def to_representation(self, instance):

return data

def get_references(self, vulnerability):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: Why we are having a method here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our old API, references used to contain the nested severity, as severity and reference were related through a foreignkey relationship. In our new model we have dissociated reference and severity. To ensure we maintain compatibility for existing users of old API we're manually crafting the references to include the relevant severity.

references = vulnerability.vulnerabilityreference_set.all()

serialized_references = VulnerabilityReferenceSerializer(
references,
context={"vulnerability": vulnerability},
many=True,
).data

return serialized_references

def get_severity_range_score(self, instance):
severity_vectors = []
severity_values = set()
for s in instance.severities:
for s in instance.severities.all():
if s.scoring_system == EPSS.identifier:
continue

Expand Down
9 changes: 4 additions & 5 deletions vulnerabilities/api_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,10 @@ class Meta:

class V2VulnerabilitySeveritySerializer(ModelSerializer):
score = CharField(source="value")
reference = V2VulnerabilityReferenceSerializer()

class Meta:
model = VulnerabilitySeverity
fields = ("score", "scoring_system", "scoring_elements", "published_at", "reference")
fields = ("url", "score", "scoring_system", "scoring_elements", "published_at")


class V2WeaknessSerializer(ModelSerializer):
Expand Down Expand Up @@ -127,9 +126,9 @@ class V2VulnerabilitySerializer(ModelSerializer):

aliases = SerializerMethodField("get_aliases")
weaknesses = V2WeaknessSerializer(many=True, source="weaknesses_set")
scores = V2VulnerabilitySeveritySerializer(many=True, source="vulnerabilityseverity_set")
references = V2VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set")
exploits = V2ExploitSerializer(many=True, source="weaknesses")
severities = V2VulnerabilitySeveritySerializer(many=True)

def get_aliases(self, vulnerability):
return vulnerability.aliases.only("alias").values_list("alias", flat=True)
Expand All @@ -145,11 +144,11 @@ class Meta:
"vulnerability_id",
"aliases",
"status",
"scores",
"weaknesses",
"summary",
"exploits",
"references",
"severities",
)


Expand Down Expand Up @@ -358,7 +357,7 @@ def get_queryset(self):
.get_queryset()
.prefetch_related(
"weaknesses",
# "severities",
"severities",
# "exploits",
)
)
Expand Down
19 changes: 14 additions & 5 deletions vulnerabilities/api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from rest_framework.response import Response
from rest_framework.reverse import reverse

from vulnerabilities.api import VulnerabilitySeveritySerializer
from vulnerabilities.models import Package
from vulnerabilities.models import Vulnerability
from vulnerabilities.models import VulnerabilityReference
Expand Down Expand Up @@ -41,11 +40,24 @@ class Meta:
fields = ["url", "reference_type", "reference_id"]


class VulnerabilitySeverityV2Serializer(serializers.ModelSerializer):
class Meta:
model = VulnerabilitySeverity
fields = ["url", "value", "scoring_system", "scoring_elements", "published_at"]

def to_representation(self, instance):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to_representation method here? if published_at is None, let it be sent as None

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this just to preserve the response structure we're already using in APIv2 https://github.com/aboutcode-org/vulnerablecode/blob/8a68c97dfa369ad048de3ece14cc1b3cf40591cc/vulnerabilities/api_v2.py#L16C33-L16C64.

IMO yes we should simpy send None when we don't have published date.

data = super().to_representation(instance)
published_at = data.get("published_at", None)
if not published_at:
data.pop("published_at")
return data


class VulnerabilityV2Serializer(serializers.ModelSerializer):
aliases = serializers.SerializerMethodField()
weaknesses = WeaknessV2Serializer(many=True)
references = VulnerabilityReferenceV2Serializer(many=True, source="vulnerabilityreference_set")
severities = VulnerabilitySeveritySerializer(many=True)
severities = VulnerabilitySeverityV2Serializer(many=True)

class Meta:
model = Vulnerability
Expand All @@ -61,9 +73,6 @@ class Meta:
def get_aliases(self, obj):
return [alias.alias for alias in obj.aliases.all()]

def get_severities(self, obj):
return obj.severities


class VulnerabilityListSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
Expand Down
25 changes: 14 additions & 11 deletions vulnerabilities/import_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,33 +180,36 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
reference_id=ref.reference_id,
url=ref.url,
)
if not reference:
continue

VulnerabilityRelatedReference.objects.update_or_create(
reference=reference,
vulnerability=vulnerability,
)
if reference:
VulnerabilityRelatedReference.objects.update_or_create(
reference=reference,
vulnerability=vulnerability,
)
updated = False
for severity in ref.severities:
try:
published_at = str(severity.published_at) if severity.published_at else None
_vs, updated = VulnerabilitySeverity.objects.update_or_create(
(
vulnerability_severity,
updated,
) = VulnerabilitySeverity.objects.update_or_create(
scoring_system=severity.system.identifier,
reference=reference,
url=ref.url,
value=severity.value,
scoring_elements=severity.scoring_elements,
defaults={
"value": str(severity.value),
"scoring_elements": str(severity.scoring_elements),
"published_at": published_at,
},
)
vulnerability.severities.add(vulnerability_severity)
except:
logger.error(
f"Failed to create VulnerabilitySeverity for: {severity} with error:\n{traceback_format_exc()}"
)
if updated:
logger.info(
f"Severity updated for reference {ref!r} to value: {severity.value!r} "
f"Severity updated for reference {ref.url!r} to value: {severity.value!r} "
f"and scoring_elements: {severity.scoring_elements!r}"
)

Expand Down
16 changes: 4 additions & 12 deletions vulnerabilities/management/commands/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,12 @@


def serialize_severity(sev):
# inlines refs
ref = sev.reference
sevref = {
"url": ref.url,
"reference_type": ref.reference_type,
"reference_id": ref.reference_id,
}

return {
"score": sev.value,
"scoring_system": sev.scoring_system,
"scoring_elements": sev.scoring_elements,
"published_at": sev.published_at,
"reference": sevref,
"url": sev.url,
}


Expand All @@ -44,7 +36,7 @@ def serialize_vulnerability(vuln):
Return a plain data mapping seralized from ``vuln`` Vulnerability instance.
"""
aliases = list(vuln.aliases.values_list("alias", flat=True))
severities = [serialize_severity(sev) for sev in vuln.severities]
severities = [serialize_severity(sev) for sev in vuln.severities.all()]
weaknesses = [wkns.cwe for wkns in vuln.weaknesses.all()]

references = list(
Expand Down Expand Up @@ -161,11 +153,11 @@ def packages_by_type_ns_name():
"affected_by_vulnerabilities",
"affected_by_vulnerabilities__references",
"affected_by_vulnerabilities__weaknesses",
"affected_by_vulnerabilities__references__vulnerabilityseverity_set",
"affected_by_vulnerabilities__severities",
"fixing_vulnerabilities",
"fixing_vulnerabilities__references",
"fixing_vulnerabilities__weaknesses",
"fixing_vulnerabilities__references__vulnerabilityseverity_set",
"fixing_vulnerabilities__severities",
)
.paginated()
)
Expand Down
Loading
Loading