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

Improve/scores implementation groups #339

Merged
merged 21 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Generated by Django 5.0.4 on 2024-04-28 15:34
# well-known scores added manually

from django.db import migrations, models


WELL_KNOWN_SCORES = {
"urn:intuitem:risk:framework:tisax-v6.0.2": (0, 5),
"urn:intuitem:risk:framework:ccb-cff-2023-03-01": (1, 5),
"urn:intuitem:risk:framework:nist-csf-2.0": (1, 4),
}


def fix_well_known_scores(apps, schema_editor):
Framework = apps.get_model("core", "Framework")
ComplianceAssessment = apps.get_model("core", "ComplianceAssessment")
for framework in Framework.objects.all():
if framework.urn in WELL_KNOWN_SCORES:
(framework.min_score, framework.max_score) = WELL_KNOWN_SCORES[
framework.urn
]
framework.save()
print("custom migration for", framework.urn)
for assessment in ComplianceAssessment.objects.all():
if assessment.framework.urn in WELL_KNOWN_SCORES:
(assessment.min_score, assessment.max_score) = WELL_KNOWN_SCORES[
assessment.framework.urn
]
print("custom migration for", assessment.framework.urn)
else:
# no default value, so fix it now
(assessment.min_score, assessment.max_score) = (0, 100)
assessment.save()


class Migration(migrations.Migration):
dependencies = [
("core", "0009_framework_max_score_framework_min_score_and_more"),
]

operations = [
migrations.RenameField(
model_name="framework",
old_name="score_definition",
new_name="scores_definition",
),
migrations.RemoveField(
model_name="requirementnode",
name="level",
),
migrations.RemoveField(
model_name="requirementnode",
name="maturity",
),
migrations.AddField(
model_name="complianceassessment",
name="max_score",
field=models.IntegerField(null=True, verbose_name="Maximum score"),
),
migrations.AddField(
model_name="complianceassessment",
name="min_score",
field=models.IntegerField(null=True, verbose_name="Minimum score"),
),
migrations.AddField(
model_name="complianceassessment",
name="scores_definition",
field=models.JSONField(
blank=True, null=True, verbose_name="Score definition"
),
),
migrations.AddField(
model_name="complianceassessment",
name="selected_implementation_groups",
field=models.JSONField(
blank=True, null=True, verbose_name="Selected implementation groups"
),
),
migrations.AddField(
model_name="framework",
name="implementation_groups_definition",
field=models.JSONField(
blank=True, null=True, verbose_name="Implementation groups definition"
),
),
migrations.AddField(
model_name="requirementassessment",
name="selected",
field=models.BooleanField(default=True, verbose_name="Selected"),
),
migrations.AddField(
model_name="requirementnode",
name="implementation_groups",
field=models.JSONField(null=True, verbose_name="Implementation groups"),
),
migrations.DeleteModel(
name="RequirementLevel",
),
migrations.RunPython(fix_well_known_scores),
]
29 changes: 27 additions & 2 deletions backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,12 @@ def __str__(self) -> str:
class Framework(ReferentialObjectMixin):
min_score = models.IntegerField(default=0, verbose_name=_("Minimum score"))
max_score = models.IntegerField(default=100, verbose_name=_("Maximum score"))
score_definition = models.JSONField(
scores_definition = models.JSONField(
blank=True, null=True, verbose_name=_("Score definition")
)
implementation_groups_definition = models.JSONField(
blank=True, null=True, verbose_name=_("Implementation groups definition")
)
library = models.ForeignKey(
Library,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -411,7 +414,9 @@ class RequirementNode(ReferentialObjectMixin):
max_length=100, null=True, blank=True, verbose_name=_("Parent URN")
)
order_id = models.IntegerField(null=True, verbose_name=_("Order ID"))
maturity = models.IntegerField(null=True, verbose_name=_("Maturity"))
implementation_groups = models.JSONField(
null=True, verbose_name=_("Implementation groups")
)
assessable = models.BooleanField(null=False, verbose_name=_("Assessable"))

class Meta:
Expand Down Expand Up @@ -1257,11 +1262,27 @@ class Result(models.TextChoices):
choices=Result.choices,
verbose_name=_("Result"),
)
selected_implementation_groups = models.JSONField(
blank=True, null=True, verbose_name=_("Selected implementation groups")
)
# score system is suggested by the framework, but can be changed at the start of the assessment
min_score = models.IntegerField(null=True, verbose_name=_("Minimum score"))
max_score = models.IntegerField(null=True, verbose_name=_("Maximum score"))
scores_definition = models.JSONField(
blank=True, null=True, verbose_name=_("Score definition")
)

class Meta:
verbose_name = _("Compliance assessment")
verbose_name_plural = _("Compliance assessments")

def save(self, *args, **kwargs) -> None:
if self.min_score is None:
self.min_score = self.framework.min_score
self.max_score = self.framework.max_score
self.scores_definition = self.framework.scores_definition
super().save(*args, **kwargs)

def get_global_score(self):
requirement_assessments_scored = (
RequirementAssessment.objects.filter(compliance_assessment=self)
Expand Down Expand Up @@ -1495,6 +1516,10 @@ class Status(models.TextChoices):
verbose_name=_("Applied controls"),
related_name="requirement_assessments",
)
selected = models.BooleanField(
default=True,
verbose_name=_("Selected"),
)

def __str__(self) -> str:
return self.requirement.display_short
Expand Down
21 changes: 13 additions & 8 deletions backend/core/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,30 +489,35 @@ class Meta:

class RequirementAssessmentWriteSerializer(BaseModelSerializer):
def validate_score(self, value):
framework = self.get_framework()
compliance_assessment = self.get_compliance_assessment()

if value is not None:
if value < framework.min_score or value > framework.max_score:
if (
value < compliance_assessment.min_score
or value > compliance_assessment.max_score
):
raise serializers.ValidationError(
{
"score": f"Score must be between {framework.min_score} and {framework.max_score}"
"score": f"Score must be between {compliance_assessment.min_score} and {compliance_assessment.max_score}"
}
)
return value

def get_framework(self):
def get_compliance_assessment(self):
if hasattr(self, "instance") and self.instance:
return self.instance.compliance_assessment.framework
return self.instance.compliance_assessment
try:
compliance_assessment_id = self.context.get("request", {}).data.get(
"compliance_assessment", {}
)
compliance_assessment = ComplianceAssessment.objects.get(
id=compliance_assessment_id
)
return compliance_assessment.framework
except Framework.DoesNotExist:
raise serializers.ValidationError("The specified framework does not exist.")
return compliance_assessment
except ComplianceAssessment.DoesNotExist:
raise serializers.ValidationError(
"The specified Compliance Assessment does not exist."
)

class Meta:
model = RequirementAssessment
Expand Down
6 changes: 3 additions & 3 deletions backend/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1217,9 +1217,9 @@ def global_score(self, request, pk):
return Response(
{
"score": self.get_object().get_global_score(),
"max_score": self.get_object().framework.max_score,
"min_score": self.get_object().framework.min_score,
"score_definition": self.get_object().framework.score_definition,
"max_score": self.get_object().max_score,
"min_score": self.get_object().min_score,
"scores_definition": self.get_object().scores_definition,
}
)

Expand Down
Loading
Loading