Skip to content

Commit

Permalink
Merge pull request #339 from intuitem/improve/scores_implementation_g…
Browse files Browse the repository at this point in the history
…roups

Improve/scores implementation groups
  • Loading branch information
nas-tabchiche authored Apr 30, 2024
2 parents 5a047c1 + 96003fa commit a609726
Show file tree
Hide file tree
Showing 33 changed files with 3,549 additions and 3,959 deletions.
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

0 comments on commit a609726

Please sign in to comment.