diff --git a/backend/app_tests/api/test_api_requirement_assessments.py b/backend/app_tests/api/test_api_requirement_assessments.py index ef49c83e1..12dad4661 100644 --- a/backend/app_tests/api/test_api_requirement_assessments.py +++ b/backend/app_tests/api/test_api_requirement_assessments.py @@ -124,7 +124,48 @@ def test_get_requirement_assessments(self, test): "id": str(compliance_assessment.id), "str": compliance_assessment.name, }, - "requirement": str(RequirementNode.objects.all()[0].id), + "requirement": { + "str": str(RequirementNode.objects.all()[0]), + "id": str(RequirementNode.objects.all()[0].id), + "urn": RequirementNode.objects.all()[0].urn, + "annotation": RequirementNode.objects.all()[0].annotation, + "name": RequirementNode.objects.all()[0].name, + "description": RequirementNode.objects.all()[0].description, + "typical_evidence": RequirementNode.objects.all()[ + 0 + ].typical_evidence, + "ref_id": RequirementNode.objects.all()[0].ref_id, + "associated_reference_controls": RequirementNode.objects.all()[ + 0 + ].associated_reference_controls, + "associated_threats": RequirementNode.objects.all()[ + 0 + ].associated_threats, + "parent_requirement": { + "str": RequirementNode.objects.all()[0].parent_requirement.get( + "str" + ), + "urn": RequirementNode.objects.all()[0].parent_requirement.get( + "urn" + ), + "id": str( + RequirementNode.objects.all()[0].parent_requirement.get( + "id" + ) + ), + "ref_id": RequirementNode.objects.all()[ + 0 + ].parent_requirement.get("ref_id"), + "name": RequirementNode.objects.all()[0].parent_requirement.get( + "name" + ), + "description": RequirementNode.objects.all()[ + 0 + ].parent_requirement.get("description"), + } + if RequirementNode.objects.all()[0].parent_requirement + else None, + }, }, base_count=-1, user_group=test.user_group, @@ -210,7 +251,48 @@ def test_update_requirement_assessments(self, test): "id": str(compliance_assessment.id), "str": compliance_assessment.name, }, - "requirement": str(RequirementNode.objects.all()[0].id), + "requirement": { + "str": str(RequirementNode.objects.all()[0]), + "id": str(RequirementNode.objects.all()[0].id), + "urn": RequirementNode.objects.all()[0].urn, + "annotation": RequirementNode.objects.all()[0].annotation, + "name": RequirementNode.objects.all()[0].name, + "description": RequirementNode.objects.all()[0].description, + "typical_evidence": RequirementNode.objects.all()[ + 0 + ].typical_evidence, + "ref_id": RequirementNode.objects.all()[0].ref_id, + "associated_reference_controls": RequirementNode.objects.all()[ + 0 + ].associated_reference_controls, + "associated_threats": RequirementNode.objects.all()[ + 0 + ].associated_threats, + "parent_requirement": { + "str": RequirementNode.objects.all()[0].parent_requirement.get( + "str" + ), + "urn": RequirementNode.objects.all()[0].parent_requirement.get( + "urn" + ), + "id": str( + RequirementNode.objects.all()[0].parent_requirement.get( + "id" + ) + ), + "ref_id": RequirementNode.objects.all()[ + 0 + ].parent_requirement.get("ref_id"), + "name": RequirementNode.objects.all()[0].parent_requirement.get( + "name" + ), + "description": RequirementNode.objects.all()[ + 0 + ].parent_requirement.get("description"), + } + if RequirementNode.objects.all()[0].parent_requirement + else None, + }, }, user_group=test.user_group, ) diff --git a/backend/core/management/commands/status.py b/backend/core/management/commands/status.py index bbee0ed8e..f8d345a8b 100644 --- a/backend/core/management/commands/status.py +++ b/backend/core/management/commands/status.py @@ -22,8 +22,9 @@ def handle(self, *args, **kwargs): nb_risk_assessments = RiskAssessment.objects.all().count() nb_risk_scenarios = RiskScenario.objects.all().count() nb_risk_acceptances = RiskAcceptance.objects.all().count() - nb_seats_available = getattr(settings, "LICENSE_SEATS", 0) - nb_expiry_date = getattr(settings, "LICENSE_EXPIRATION", "") + nb_seats = getattr(settings, "LICENSE_SEATS", 0) + nb_editors = len(User.get_editors()) + expiration = getattr(settings, "LICENSE_EXPIRATION", "") created_at = Folder.get_root_folder().created_at last_login = max( @@ -41,5 +42,5 @@ def handle(self, *args, **kwargs): + f"threats={nb_threats} functions={nb_functions} measures={nb_measures} " + f"evidences={nb_evidences} compliance={nb_compliance_assessments} risk={nb_risk_assessments} " + f"scenarios={nb_risk_scenarios} acceptances={nb_risk_acceptances} " - + f"number_of_seats={nb_seats_available} expiry_date={nb_expiry_date}" + + f"seats={nb_seats} editors={nb_editors} expiration={expiration}" ) diff --git a/backend/core/models.py b/backend/core/models.py index 93fc5bd45..fa0d57fb9 100644 --- a/backend/core/models.py +++ b/backend/core/models.py @@ -1049,6 +1049,40 @@ class RequirementNode(ReferentialObjectMixin, I18nObjectMixin): ) question = models.JSONField(blank=True, null=True, verbose_name=_("Question")) + @property + def associated_reference_controls(self): + _reference_controls = self.reference_controls.all() + reference_controls = [] + for control in _reference_controls: + reference_controls.append( + {"str": control.display_long, "urn": control.urn, "id": control.id} + ) + return reference_controls + + @property + def associated_threats(self): + _threats = self.threats.all() + threats = [] + for control in _threats: + threats.append( + {"str": control.display_long, "urn": control.urn, "id": control.id} + ) + return threats + + @property + def parent_requirement(self): + parent_requirement = RequirementNode.objects.filter(urn=self.parent_urn).first() + if not parent_requirement: + return None + return { + "str": parent_requirement.display_long, + "urn": parent_requirement.urn, + "id": parent_requirement.id, + "ref_id": parent_requirement.ref_id, + "name": parent_requirement.name, + "description": parent_requirement.description, + } + class Meta: verbose_name = _("RequirementNode") verbose_name_plural = _("RequirementNodes") diff --git a/backend/core/serializers.py b/backend/core/serializers.py index e42d8c9e2..1b2661a14 100644 --- a/backend/core/serializers.py +++ b/backend/core/serializers.py @@ -612,6 +612,20 @@ class RequirementAssessmentReadSerializer(BaseModelSerializer): compliance_assessment = FieldsRelatedField() folder = FieldsRelatedField() assessable = serializers.BooleanField(source="requirement.assessable") + requirement = FieldsRelatedField( + [ + "id", + "urn", + "annotation", + "name", + "description", + "typical_evidence", + "ref_id", + "associated_reference_controls", + "associated_threats", + "parent_requirement", + ] + ) class Meta: model = RequirementAssessment diff --git a/backend/core/startup.py b/backend/core/startup.py index 0b5294dc5..f3faad47d 100644 --- a/backend/core/startup.py +++ b/backend/core/startup.py @@ -345,7 +345,6 @@ "view_complianceassessment", "view_requirementassessment", "change_requirementassessment", - "view_requirementnode", "view_evidence", "add_evidence", "change_evidence", diff --git a/enterprise/frontend/src/routes/(authentication)/login/+page.svelte b/enterprise/frontend/src/routes/(authentication)/login/+page.svelte index f34fdac3d..b5269596d 100644 --- a/enterprise/frontend/src/routes/(authentication)/login/+page.svelte +++ b/enterprise/frontend/src/routes/(authentication)/login/+page.svelte @@ -8,18 +8,29 @@ export let form: ActionData; -
-
-
+
+ -
-
+
+
+ {#if !data.clientSettings.settings.name} + + {/if} +
+
+
+
-
+
diff --git a/enterprise/frontend/src/routes/(authentication)/login/Greetings.svelte b/enterprise/frontend/src/routes/(authentication)/login/Greetings.svelte index 7b667197d..070dbd6ff 100644 --- a/enterprise/frontend/src/routes/(authentication)/login/Greetings.svelte +++ b/enterprise/frontend/src/routes/(authentication)/login/Greetings.svelte @@ -7,7 +7,10 @@ const clientSettings = $page.data.clientSettings; -
+
{#if !clientSettings?.settings.name}
diff --git a/enterprise/update-ciso-assistant-enterprise.sh b/enterprise/update-ciso-assistant-enterprise.sh new file mode 100755 index 000000000..699a060e8 --- /dev/null +++ b/enterprise/update-ciso-assistant-enterprise.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env bash +pushd .. +./update-ciso-assistant.sh enterprise +popd diff --git a/frontend/src/lib/utils/crud.ts b/frontend/src/lib/utils/crud.ts index 425dca1ec..52a7e0830 100644 --- a/frontend/src/lib/utils/crud.ts +++ b/frontend/src/lib/utils/crud.ts @@ -430,7 +430,11 @@ export const URL_MODEL_MAP: ModelMap = { verboseNamePlural: 'Evidences', fileFields: ['attachment'], foreignKeyFields: [ - { field: 'folder', urlModel: 'folders', urlParams: 'content_type=DO&content_type=GL' }, + { + field: 'folder', + urlModel: 'folders', + urlParams: 'content_type=DO&content_type=GL&content_type=EN' + }, { field: 'applied_controls', urlModel: 'applied-controls' } ] }, diff --git a/frontend/src/routes/(app)/(internal)/compliance-assessments/[id=uuid]/flash-mode/+page.svelte b/frontend/src/routes/(app)/(internal)/compliance-assessments/[id=uuid]/flash-mode/+page.svelte index 76913b580..8b0dae806 100644 --- a/frontend/src/routes/(app)/(internal)/compliance-assessments/[id=uuid]/flash-mode/+page.svelte +++ b/frontend/src/routes/(app)/(internal)/compliance-assessments/[id=uuid]/flash-mode/+page.svelte @@ -26,7 +26,7 @@ const requirementHashmap = Object.fromEntries( data.requirements.map((requirement) => [requirement.id, requirement]) ); - $: requirement = requirementHashmap[currentRequirementAssessment.requirement]; + $: requirement = requirementHashmap[currentRequirementAssessment.requirement.id]; $: parent = data.requirements.find((req) => req.urn === requirement.parent_urn); $: title = requirement.display_short diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts index acb0fb1c7..177c5ff37 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.server.ts @@ -12,13 +12,8 @@ export const load = (async ({ fetch, params }) => { const complianceAssessmentScore = await fetch( `${BASE_API_URL}/compliance-assessments/${requirementAssessment.compliance_assessment.id}/global_score/` ).then((res) => res.json()); - const requirement = await fetch( - `${BASE_API_URL}/requirement-nodes/${requirementAssessment.requirement}/` - ).then((res) => res.json()); - const parentRequirementNodeEndpoint = `${BASE_API_URL}/requirement-nodes/?urn=${requirement.parent_urn}`; - const parent = await fetch(parentRequirementNodeEndpoint) - .then((res) => res.json()) - .then((res) => res.results[0]); + const requirement = requirementAssessment.requirement; + const parent = requirementAssessment.requirement.parent_requirement; const tables: Record = {}; diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte index 33163029d..58cf6fa78 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/+page.svelte @@ -12,13 +12,14 @@ import { page } from '$app/stores'; export let data: PageData; - const threats = data.requirement.threats; - const reference_controls = data.requirement.reference_controls; + const threats = data.requirementAssessment.requirement.associated_threats ?? []; + const reference_controls = + data.requirementAssessment.requirement.associated_reference_controls ?? []; const annotation = data.requirement.annotation; const typical_evidence = data.requirement.typical_evidence; - const has_threats = threats && threats.length > 0; - const has_reference_controls = reference_controls && reference_controls.length > 0; + const has_threats = threats.length > 0; + const has_reference_controls = reference_controls.length > 0; $: mappingInference = { sourceRequirementAssessment: diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts index a3fd98236..08e68fed1 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.server.ts @@ -29,16 +29,12 @@ export const load = (async ({ fetch, params }) => { } const requirementAssessment = await fetchJson(endpoint); - const [compliance_assessment_score, requirement] = await Promise.all([ - fetchJson( - `${baseUrl}/compliance-assessments/${requirementAssessment.compliance_assessment.id}/global_score/` - ), - fetchJson(`${baseUrl}/requirement-nodes/${requirementAssessment.requirement}/`) - ]); - - const parent = await fetchJson( - `${baseUrl}/requirement-nodes/?urn=${requirement.parent_urn}` - ).then((res) => res.results[0]); + const requirement = requirementAssessment.requirement; + const compliance_assessment_score = await fetchJson( + `${baseUrl}/compliance-assessments/${requirementAssessment.compliance_assessment.id}/global_score/` + ); + + const parent = requirementAssessment.requirement.parent_requirement; const model = getModelInfo(URLModel); const object = { ...requirementAssessment }; diff --git a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte index 8b247aecc..e03b5b656 100644 --- a/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte +++ b/frontend/src/routes/(app)/(third-party)/requirement-assessments/[id=uuid]/edit/+page.svelte @@ -6,13 +6,14 @@ export let data: PageData; export let form: ActionData; - const threats = data.requirement.threats; - const reference_controls = data.requirement.reference_controls; + const threats = data.requirementAssessment.requirement.associated_threats ?? []; + const reference_controls = + data.requirementAssessment.requirement.associated_reference_controls ?? []; const annotation = data.requirement.annotation; const typical_evidence = data.requirement.typical_evidence; - const has_threats = threats && threats.length > 0; - const has_reference_controls = reference_controls && reference_controls.length > 0; + const has_threats = threats.length > 0; + const has_reference_controls = reference_controls.length > 0; import { page } from '$app/stores'; import AutocompleteSelect from '$lib/components/Forms/AutocompleteSelect.svelte'; diff --git a/frontend/src/routes/(authentication)/login/+page.svelte b/frontend/src/routes/(authentication)/login/+page.svelte index d7fcc0f30..2726cba53 100644 --- a/frontend/src/routes/(authentication)/login/+page.svelte +++ b/frontend/src/routes/(authentication)/login/+page.svelte @@ -8,16 +8,25 @@ export let form: ActionData; -
-
-
+
+ -
-
+
+
+ +
+
+
+
-
+
diff --git a/frontend/src/routes/(authentication)/login/FormCard.svelte b/frontend/src/routes/(authentication)/login/FormCard.svelte index 2baaa8ef5..fbe008c82 100644 --- a/frontend/src/routes/(authentication)/login/FormCard.svelte +++ b/frontend/src/routes/(authentication)/login/FormCard.svelte @@ -42,7 +42,7 @@ $: form && form.mfaFlow ? modalMFAAuthenticate() : null; -
+
diff --git a/frontend/src/routes/(authentication)/login/Greetings.svelte b/frontend/src/routes/(authentication)/login/Greetings.svelte index d21c67ca3..83390118a 100644 --- a/frontend/src/routes/(authentication)/login/Greetings.svelte +++ b/frontend/src/routes/(authentication)/login/Greetings.svelte @@ -3,7 +3,10 @@ import Typewriter from 'svelte-typewriter'; -
+
{m.helloThere()} diff --git a/frontend/src/routes/(authentication)/password-reset/+page.svelte b/frontend/src/routes/(authentication)/password-reset/+page.svelte index f78f314b4..cf4b04955 100644 --- a/frontend/src/routes/(authentication)/password-reset/+page.svelte +++ b/frontend/src/routes/(authentication)/password-reset/+page.svelte @@ -18,7 +18,7 @@
-
+
diff --git a/update-ciso-assistant.sh b/update-ciso-assistant.sh new file mode 100755 index 000000000..ea7b67eb5 --- /dev/null +++ b/update-ciso-assistant.sh @@ -0,0 +1,48 @@ +#! /usr/bin/env bash + +VERSION=${1:-community} + +if [ "$VERSION" = "enterprise" ]; then + # Go to the enterprise directory + cd "enterprise/" || exit 1 +fi + +DB_FILE="db/ciso-assistant.sqlite3" +BACKUP_FILE="ciso-assistant-backup.sqlite3" + +if [ "$VERSION" = "enterprise" ]; then + BACKEND_IMAGE="ghcr.io/intuitem/ciso-assistant-enterprise-backend:latest" + FRONTEND_IMAGE="ghcr.io/intuitem/ciso-assistant-enterprise-frontend:latest" +else + BACKEND_IMAGE="ghcr.io/intuitem/ciso-assistant-community/backend:latest" + FRONTEND_IMAGE="ghcr.io/intuitem/ciso-assistant-community/frontend:latest" +fi + +echo "Update of the version : $VERSION" + +# Backup the database +if [ ! -f "$DB_FILE" ]; then + echo "Error: No database found, please initialize CISO Assistant first" + exit 1 +else + cp "$DB_FILE" "$BACKUP_FILE" + echo "Backup of the database created in $BACKUP_FILE" +fi + +# Stop the containers +docker compose down + +# Remove the images +docker rmi "$BACKEND_IMAGE" "$FRONTEND_IMAGE" 2> /dev/null + +# Start the containers +docker compose up -d + +# Wait for the database to be ready +echo "Giving some time for the database to be ready, please wait ..." +sleep 5 + +# Apply migrations +BACKEND_CONTAINER=$(docker ps --filter "ancestor=$BACKEND_IMAGE" --format "{{.Names}}") +docker compose exec "$BACKEND_CONTAINER" poetry run python manage.py migrate +echo "CISO assistant updated successfully"