Skip to content

Commit

Permalink
Merge pull request #277 from intuitem/CA-178-Add-score-notion-to-Comp…
Browse files Browse the repository at this point in the history
…lianceAssessment

Ca 178 add score notion to compliance assessment
  • Loading branch information
eric-intuitem authored Apr 26, 2024
2 parents 6d6a0d9 + 52cd4aa commit 1f7328e
Show file tree
Hide file tree
Showing 48 changed files with 497 additions and 110 deletions.
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_applied_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from core.models import ReferenceControl, AppliedControl
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic applied control data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from core.models import Asset
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic asset data for tests
Expand Down
7 changes: 6 additions & 1 deletion backend/app_tests/api/test_api_compliance_assessments.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from core.models import Project
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic compliance assessment data for tests
Expand Down Expand Up @@ -125,6 +124,8 @@ def test_get_compliance_assessments(self, test):
"framework": {
"id": str(Framework.objects.all()[0].id),
"str": str(Framework.objects.all()[0]),
"min_score": 0,
"max_score": 100,
},
},
user_group=test.user_group,
Expand Down Expand Up @@ -156,6 +157,8 @@ def test_create_compliance_assessments(self, test):
"framework": {
"id": str(Framework.objects.all()[0].id),
"str": str(Framework.objects.all()[0]),
"min_score": Framework.objects.all()[0].min_score,
"max_score": Framework.objects.all()[0].max_score,
},
},
user_group=test.user_group,
Expand Down Expand Up @@ -199,6 +202,8 @@ def test_update_compliance_assessments(self, test):
"framework": {
"id": str(Framework.objects.all()[0].id),
"str": str(Framework.objects.all()[0]),
"min_score": Framework.objects.all()[0].min_score,
"max_score": Framework.objects.all()[0].max_score,
},
},
user_group=test.user_group,
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_evidences.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from core.models import Evidence
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic evidence data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from rest_framework.test import APIClient
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic folder data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from iam.models import Folder
from rest_framework import status

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries, EndpointTestsUtils


Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_policies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from core.models import Policy
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic policy data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from core.models import Project
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic project data for tests
Expand Down
3 changes: 1 addition & 2 deletions backend/app_tests/api/test_api_reference_controls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import pytest
from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_400_BAD_REQUEST
from rest_framework.status import HTTP_403_FORBIDDEN
from rest_framework.test import APIClient
from core.models import ReferenceControl
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic reference control data for tests
Expand Down
7 changes: 6 additions & 1 deletion backend/app_tests/api/test_api_requirement_assessments.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from core.models import Project, AppliedControl
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic requirement assessment data for tests
Expand Down Expand Up @@ -46,6 +45,7 @@ def test_get_requirement_assessments(self, authenticated_client):
"requirement": RequirementNode.objects.create(
name="test", folder=folder, assessable=False
),
"score": None,
},
)

Expand Down Expand Up @@ -83,6 +83,7 @@ def test_update_requirement_assessments(self, authenticated_client):
"requirement": RequirementNode.objects.create(
name="test", folder=folder, assessable=False
),
"score": None,
},
{
"status": REQUIREMENT_ASSESSMENT_STATUS2,
Expand Down Expand Up @@ -115,6 +116,7 @@ def test_get_requirement_assessments(self, test):
"folder": test.folder,
"compliance_assessment": compliance_assessment,
"requirement": RequirementNode.objects.all()[0],
"score": None,
},
{
"folder": {"id": str(test.folder.id), "str": test.folder.name},
Expand Down Expand Up @@ -151,6 +153,7 @@ def test_create_requirement_assessments(self, test):
"compliance_assessment": str(compliance_assessment.id),
"requirement": str(RequirementNode.objects.all()[0].id),
"applied_controls": [str(applied_control.id)],
"score": None,
},
{
"compliance_assessment": {
Expand Down Expand Up @@ -190,6 +193,7 @@ def test_update_requirement_assessments(self, test):
"folder": test.folder,
"compliance_assessment": compliance_assessment,
"requirement": RequirementNode.objects.all()[0],
"score": None,
},
{
"status": REQUIREMENT_ASSESSMENT_STATUS2,
Expand All @@ -198,6 +202,7 @@ def test_update_requirement_assessments(self, test):
"compliance_assessment": str(compliance_assessment2.id),
"requirement": str(RequirementNode.objects.all()[1].id),
"applied_controls": [str(applied_control.id)],
"score": 50,
},
{
"folder": {"id": str(test.folder.id), "str": test.folder.name},
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_requirement_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from core.models import RequirementNode, Framework
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries, EndpointTestsUtils

# Generic requirement data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_risk_acceptances.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
)
from iam.models import Folder, UserGroup

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic risk acceptance data for tests
Expand Down
3 changes: 1 addition & 2 deletions backend/app_tests/api/test_api_risk_assessments.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import pytest
from rest_framework.test import APIClient
from core.models import Project, RiskAssessment, RiskMatrix
from iam.models import Folder, User
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic project data for tests
Expand Down
1 change: 0 additions & 1 deletion backend/app_tests/api/test_api_risk_scenarios.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
)
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic project data for tests
Expand Down
3 changes: 1 addition & 2 deletions backend/app_tests/api/test_api_threats.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import pytest
from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_400_BAD_REQUEST
from rest_framework.status import HTTP_403_FORBIDDEN
from rest_framework.test import APIClient
from core.models import Threat
from iam.models import Folder

from test_vars import GROUPS_PERMISSIONS
from test_utils import EndpointTestsQueries

# Generic threat data for tests
Expand Down
3 changes: 1 addition & 2 deletions backend/app_tests/api/test_api_user_groups.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import pytest
from core.models import User

from iam.models import Folder, UserGroup, RoleAssignment
from iam.models import RoleAssignment
from test_vars import GROUPS_PERMISSIONS, TEST_USER_EMAIL
from test_utils import EndpointTestsQueries


@pytest.mark.django_db
Expand Down
2 changes: 1 addition & 1 deletion backend/app_tests/api/test_api_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from rest_framework.test import APIClient
from iam.models import User

from test_vars import GROUPS_PERMISSIONS, USERS_ENDPOINT as API_ENDPOINT
from test_vars import USERS_ENDPOINT as API_ENDPOINT
from test_utils import EndpointTestsQueries

# Generic user data for tests
Expand Down
7 changes: 3 additions & 4 deletions backend/app_tests/api/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from ciso_assistant.settings import EMAIL_HOST, EMAIL_HOST_RESCUE

from test_vars import *

Expand Down Expand Up @@ -753,7 +752,7 @@ def update_object(
), f"{verbose_name} object detail can be accessed outside the domain"
else:
if (
verbose_name is not "Users"
verbose_name != "Users"
): # Users don't have permission to view users details
assert (
response.status_code == status.HTTP_200_OK
Expand Down Expand Up @@ -893,7 +892,7 @@ def delete_object(
), f"{verbose_name} object detail can be accessed outside the domain"
else:
if (
verbose_name is not "Users"
verbose_name != "Users"
): # Users don't have permission to view users details
assert (
response.status_code == status.HTTP_200_OK
Expand Down Expand Up @@ -1013,7 +1012,7 @@ def compare_results(
reference = authenticated_client.get(reference_url)
assert (
reference.status_code == status.HTTP_200_OK
), f"reference endpoint is not accessible"
), "reference endpoint is not accessible"

for object in reference.json()["objects"]["framework"][
object_name.lower().replace(" ", "_")
Expand Down
2 changes: 0 additions & 2 deletions backend/core/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from django.core.exceptions import ValidationError
import uuid

from ciso_assistant import settings


class AbstractBaseModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Expand Down
8 changes: 8 additions & 0 deletions backend/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ def get_sorted_requirement_nodes_rec(
"name": node.name,
"ra_id": str(req_as.id) if requirements_assessed else None,
"status": req_as.status if requirements_assessed else None,
"is_scored": req_as.is_scored if requirements_assessed else None,
"score": req_as.score if requirements_assessed else None,
"max_score": req_as.compliance_assessment.framework.max_score
if requirements_assessed
else None,
"status_display": req_as.get_status_display()
if requirements_assessed
else None,
Expand Down Expand Up @@ -274,6 +279,9 @@ def get_sorted_requirement_nodes_rec(
"description": req.description,
"ra_id": str(req_as.id),
"status": req_as.status,
"is_scored": req_as.is_scored,
"score": req_as.score,
"max_score": req_as.compliance_assessment.framework.max_score,
"status_display": req_as.get_status_display(),
"status_i18n": camel_case(req_as.status),
"style": "leaf",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Generated by Django 5.0.2 on 2024-03-04 20:07

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


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 5.0.4 on 2024-04-25 14:20

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("core", "0008_alter_complianceassessment_status_and_more"),
]

operations = [
migrations.AddField(
model_name="framework",
name="max_score",
field=models.IntegerField(default=100, verbose_name="Maximum score"),
),
migrations.AddField(
model_name="framework",
name="min_score",
field=models.IntegerField(default=0, verbose_name="Minimum score"),
),
migrations.AddField(
model_name="framework",
name="score_definition",
field=models.JSONField(
blank=True, null=True, verbose_name="Score definition"
),
),
migrations.AddField(
model_name="requirementassessment",
name="is_scored",
field=models.BooleanField(default=False, verbose_name="Is scored"),
),
migrations.AddField(
model_name="requirementassessment",
name="score",
field=models.IntegerField(blank=True, null=True, verbose_name="Score"),
),
]
27 changes: 26 additions & 1 deletion backend/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from django.contrib.auth import get_user_model
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils.html import mark_safe
from django.db.models import Q

from .base_models import *
Expand Down Expand Up @@ -328,6 +327,11 @@ 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(
blank=True, null=True, verbose_name=_("Score definition")
)
library = models.ForeignKey(
Library,
on_delete=models.CASCADE,
Expand Down Expand Up @@ -1258,6 +1262,18 @@ class Meta:
verbose_name = _("Compliance assessment")
verbose_name_plural = _("Compliance assessments")

def get_global_score(self):
requirement_assessments_scored = (
RequirementAssessment.objects.filter(compliance_assessment=self)
.exclude(score=None)
.exclude(status=RequirementAssessment.Status.NOT_APPLICABLE)
.exclude(is_scored=False)
)
score = requirement_assessments_scored.aggregate(models.Avg("score"))
if score["score__avg"] is not None:
return round(score["score__avg"], 1)
return -1

def get_requirements_status_count(self):
requirements_status_count = []
for st in RequirementAssessment.Status:
Expand Down Expand Up @@ -1448,6 +1464,15 @@ class Status(models.TextChoices):
default=Status.TODO,
verbose_name=_("Status"),
)
score = models.IntegerField(
blank=True,
null=True,
verbose_name=_("Score"),
)
is_scored = models.BooleanField(
default=False,
verbose_name=_("Is scored"),
)
evidences = models.ManyToManyField(
Evidence,
blank=True,
Expand Down
Loading

0 comments on commit 1f7328e

Please sign in to comment.