From 31e0e3a4b017e8e0b38bab2c6c2e9922d4cd7699 Mon Sep 17 00:00:00 2001
From: Jochen Kemnade <jk@sernet.de>
Date: Wed, 25 Sep 2024 14:17:46 +0200
Subject: [PATCH] add compact ISA report

verinice-veo#3177
---
 src/main/java/org/veo/reporting/Demo.java     |  23 +-
 src/main/resources/reports/tisax-compact.json |  25 ++
 src/main/resources/templates/tisax-compact.md | 279 ++++++++++++++++++
 .../templates/tisax-compact.properties        |  48 +++
 .../org/veo/reporting/ReportEngineSpec.groovy |   3 +-
 .../controllers/ReportControllerSpec.groovy   |   3 +-
 6 files changed, 376 insertions(+), 5 deletions(-)
 create mode 100644 src/main/resources/reports/tisax-compact.json
 create mode 100644 src/main/resources/templates/tisax-compact.md
 create mode 100644 src/main/resources/templates/tisax-compact.properties

diff --git a/src/main/java/org/veo/reporting/Demo.java b/src/main/java/org/veo/reporting/Demo.java
index 9180436..3b47ac4 100644
--- a/src/main/java/org/veo/reporting/Demo.java
+++ b/src/main/java/org/veo/reporting/Demo.java
@@ -64,6 +64,7 @@ static void runDemo(ConfigurableApplicationContext ctx) throws IOException {
     var requestId = ctx.getEnvironment().getProperty("veo.demorequestid");
     var scopeIdItgs = ctx.getEnvironment().getProperty("veo.demoscopeiditbp");
     var scopeIdNIS2 = ctx.getEnvironment().getProperty("veo.demoscopeidnis2");
+    var isaId = ctx.getEnvironment().getProperty("veo.demoisaid");
     var veoClient = ctx.getBean(VeoClient.class);
     var authHeader = "Bearer " + token;
     Map<Locale, Map<String, Object>> entriesForLanguage = new HashMap<>();
@@ -81,6 +82,7 @@ static void runDemo(ConfigurableApplicationContext ctx) throws IOException {
     boolean createRequestReports = requestId != null;
     boolean createItgsReports = scopeIdItgs != null;
     boolean createNIS2Reports = scopeIdNIS2 != null;
+    boolean createISAReports = isaId != null;
 
     DataProvider dataProvider =
         dataSpec -> {
@@ -105,6 +107,8 @@ static void runDemo(ConfigurableApplicationContext ctx) throws IOException {
                                   url = url.replace(TARGET_ID_PLACEHOLDER, scopeIdItgs);
                                 } else if ("organization".equals(key)) {
                                   url = url.replace(TARGET_ID_PLACEHOLDER, scopeIdNIS2);
+                                } else if ("isa".equals(key)) {
+                                  url = url.replace(TARGET_ID_PLACEHOLDER, isaId);
                                 } else if (url.contains("targetId")) {
                                   throw new IllegalArgumentException("Unhandled url: " + url);
                                 }
@@ -133,7 +137,8 @@ static void runDemo(ConfigurableApplicationContext ctx) throws IOException {
         createDPIncidentReports,
         createRequestReports,
         createItgsReports,
-        createNIS2Reports);
+        createNIS2Reports,
+        createISAReports);
     Path template = Paths.get("src/main/resources/templates");
     try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
 
@@ -161,7 +166,8 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                 createDPIncidentReports,
                 createRequestReports,
                 createItgsReports,
-                createNIS2Reports);
+                createNIS2Reports,
+                createISAReports);
           }
           key.reset();
         }
@@ -181,7 +187,8 @@ static void createReports(
       boolean createDPIncidentReports,
       boolean createRequestReports,
       boolean createItgsReports,
-      boolean createNIS2Reports)
+      boolean createNIS2Reports,
+      boolean createISAReports)
       throws IOException {
 
     ReportCreationParameters parametersGermany =
@@ -381,6 +388,16 @@ static void createReports(
             parametersUS,
             entriesForLanguage.get(Locale.US));
       }
+      if (createISAReports) {
+        createReport(
+            reportEngine,
+            "tisax-compact",
+            "/tmp/tisax-compact.pdf",
+            dataProvider,
+            MediaType.APPLICATION_PDF_VALUE,
+            parametersGermany,
+            entriesForLanguage.get(Locale.GERMANY));
+      }
     } catch (IOException | TemplateException e) {
       logger.error("Error creating reports", e);
     }
diff --git a/src/main/resources/reports/tisax-compact.json b/src/main/resources/reports/tisax-compact.json
new file mode 100644
index 0000000..3322213
--- /dev/null
+++ b/src/main/resources/reports/tisax-compact.json
@@ -0,0 +1,25 @@
+{
+  "name": {
+    "de": "ISA: Information Security Assessment 6"
+  },
+  "domainName": "TISAX",
+  "description": {
+    "de": "ISA: Information Security Assessment 6"
+  },
+  "templateFile": "tisax-compact.md",
+  "templateType": "text/markdown",
+  "outputTypes": [
+    "application/pdf"
+  ],
+  "targetTypes": [
+    {
+      "modelType": "scope",
+      "subTypes": [
+        "SCP_InformationSecurityAssessment"
+      ]
+    }
+  ],
+  "data": {
+    "isa": "/scopes/${targetId}"
+  }
+}
diff --git a/src/main/resources/templates/tisax-compact.md b/src/main/resources/templates/tisax-compact.md
new file mode 100644
index 0000000..7ca71c3
--- /dev/null
+++ b/src/main/resources/templates/tisax-compact.md
@@ -0,0 +1,279 @@
+<#import "/libs/commons.md" as com>
+
+<#assign table = com.table
+         def = com.def
+         multiline = com.multiline
+         heading = com.heading />
+
+<style>
+<#include "styles/default.css">
+h1, h2, h3, h4, h5, h6 {
+  page-break-after: avoid;
+}
+
+td {
+  vertical-align: top;
+}
+
+.main_page {
+  page-break-after: always;
+}
+
+.main_page table th:first-child, .main_page table td:first-child {
+  width: 8cm;
+  padding-left:0;
+}
+
+.fullwidth {
+  width: 100%;
+}
+
+.nobreak {
+  page-break-inside: avoid;
+}
+
+.maturityred {
+  color: #f00;
+}
+
+.maturityorange {
+  color: #f90;
+}
+
+.maturitygreen {
+  color: #396;
+}
+
+table.maturitytable > tbody > tr > td:nth-child(3),
+table.maturitytable > tbody > tr > td:nth-child(4) {
+  text-align: center;
+}
+
+.paginated {
+  -fs-table-paginate: paginate;
+}
+</style>
+
+<#assign domain=domains?filter(it->it.name == 'TISAX')?filter(it->isa.domains?keys?seq_contains(it.id))?sort_by("createdAt")?last />
+
+<#assign scope = scopes?filter(it->it.domains[domain.id].subType == 'SCP_Organization')?filter(it->it.members?map(it->it._self)?seq_contains(isa._self))?first! />
+
+<#assign usedControls = isa.getMembersWithType('control')>
+
+
+<div class="footer-left">
+  <table>
+    <tr>
+      <td>Scope: </td>
+      <td>${scope.name!}</td>
+    </tr>
+    <tr>
+      <td>${bundle.creation_date}: </td>
+      <td>${.now?date}</td>
+    </tr>
+  </table>
+</div>
+
+<div class="cover">
+<h1><@multiline bundle.title/></h1>
+<p>powered by verinice</p>
+</div>
+
+
+# ${bundle.main_page} {#main_page}
+
+<h2>Information Security Assessment<br/>${isa.name}<br/>kompakt</h2>
+<div class="main_page">
+
+<#if scope?has_content>
+
+<@table bundle.scope_SCP_Organization_singular,
+scope,
+[{'name' : 'abbreviation name'},
+'scope_address_address1',
+'scope_address_address2',
+{'scope_address_postcode, scope_address_city' : 'scope_address_postcode scope_address_city'},
+{'scope_address_country, scope_address_state' : 'scope_address_country scope_address_state'},
+'scope_identification_duns'
+],
+domain/>
+
+</#if>
+
+
+<@table bundle.scope_SCP_InformationSecurityAssessment_singular,
+isa,
+['scope_tisax_scopeID',
+{'isa_scope' : 'description'},
+'scope_tisax_assessmentDate'
+],
+domain/>
+
+<#assign tisaxContact = isa.findFirstLinked('scope_tisaxContact') !>
+
+<#if tisaxContact?has_content>
+
+<@table 'Ansprechpartner',
+  tisaxContact,
+  [
+   {'name' : 'person_generalInformation_givenName person_generalInformation_familyName'},
+   'person_contactInformation_office / person_contactInformation_mobile',
+   'person_contactInformation_email'
+  ],
+domain/>
+
+</#if>
+
+<#assign tisaxCreator = isa.findFirstLinked('scope_tisaxCreator') !>
+
+<#if tisaxCreator?has_content>
+
+<@table 'Ersteller',
+  tisaxCreator,
+  [
+   {'name' : 'person_generalInformation_givenName person_generalInformation_familyName'}
+  ],
+domain/>
+
+</#if>
+
+
+<div style="margin-top:1cm; margin-bottom:2cm">
+Unterschrift:
+</div>
+
+Version 6.0.2
+
+</div>
+
+<#function averageMaturity controls cutback=-1>
+<#local maturities = controls?map(it->it.control_isaMaturity_assessment!)?filter(it->it?has_content)?map(it->it?keep_after_last('_')?number) />
+<#if (maturities?size == 0)>
+<#return 0>
+</#if>
+<#local sum = 0>
+<#list maturities as it>
+<#if (cutback>0)>
+<#local sum = sum + [it, cutback]?min>
+<#else>
+<#local sum = sum + it>
+</#if>>
+</#list>
+<#return sum / maturities?size>
+</#function>
+
+
+| ISA Ergebnisse    | | | |
+|:---|:---|:---|:---|
+| Ergebnis mit Kürzung auf Zielreifegrad  | ${averageMaturity(usedControls, 3)?string["0.00"]}  | Höchstes erreichbares Ergebnis:| ${3.00?string["0.00"]} |
+{.table .fullwidth .paginated}
+
+<#function unique_items seq>
+  <#local result = []>
+  <#list seq as item>
+    <#if ! result?seq_contains(item)>
+      <#local result = result + [item] />
+    </#if>
+  </#list>
+  <#return result>
+</#function>
+
+
+<#assign chapters=unique_items(usedControls?filter(it->it.domains[domain.id].subType != 'CTL_ISAControlDataProtection')?map(c->c.abbreviation?keep_before(".")))?sort>
+
+<object type="jfreechart/veo-spiderweb" style="margin-bottom: 2cm;width:17cm;height:15cm;margin:auto;" title="Ergebnis je Kapitel (ohne Kürzung)" alt="Diagramm: Ergebnis je Kapitel (ohne Kürzung)" interiorGap="0.4">
+<#list chapters as chapter>
+  <data row="Zielreifegrad" column="${chapter} ${bundle["chapter_label_"+chapter]}" value="3"/>
+<#assign controls_chapter = usedControls?filter(c->c.abbreviation?starts_with(chapter))>
+  <data row="Ergebnis" column="${chapter} ${bundle["chapter_label_"+chapter]}" value="${averageMaturity(controls_chapter)?c}"/>
+</#list>
+</object>
+
+<div class="pagebreak" />
+
+<#assign subchapters=unique_items(usedControls?filter(it->it.domains[domain.id].subType != 'CTL_ISAControlDataProtection')?map(c->c.abbreviation?keep_before_last(".")))?sort>
+
+<object type="jfreechart/veo-spiderweb" style="margin-bottom: 2cm;width:17cm;height:15cm;margin:auto;" title="Ergebnis je Unterkapitel (ohne Kürzung)" alt="Diagramm: Ergebnis je Unterkapitel (ohne Kürzung)" interiorGap="0.6">
+<#list subchapters as subchapter>
+  <data row="Zielreifegrad" column="${subchapter} ${bundle["chapter_label_"+subchapter]}" value="3"/>
+<#assign controls_subchapters = usedControls?filter(c->c.abbreviation?starts_with(subchapter))>
+  <data row="Ergebnis" column="${subchapter} ${bundle["chapter_label_"+subchapter]}" value="${averageMaturity(controls_subchapters)?c}"/>
+</#list>
+</object>
+
+<div class="pagebreak" />
+
+<#macro maturitydisplay control>
+<#local targetLevel = 3>
+<#local val = control.control_isaMaturity_assessment!>
+<#if val?has_content>
+<#local level=val?keep_after_last('_') />
+<#local diff = targetLevel - level?number>
+<#local class="maturitygreen"/>
+<#if (diff >= 2)>
+<#local class="maturityred"/>
+<#elseif (diff >= 1) >
+<#local class="maturityorange"/>
+</#if>
+<span class="${class}">${level}</span>
+</#if>
+</#macro>
+
+<#assign isacontrols = usedControls?filter(it->it.domains[domain.id].subType == 'CTL_ISAControlInformationSecurity')?sort_by('abbreviation')>
+<#assign prototypecontrols = usedControls?filter(it->it.domains[domain.id].subType == 'CTL_ISAControlPrototypeProtection')?sort_by('abbreviation')>
+<#assign dataprotectioncontrols = usedControls?filter(it->it.domains[domain.id].subType == 'CTL_ISAControlDataProtection')?sort_by('abbreviation')>
+
+# Information Security Assessment <br/> Ergebnisse - Informationssicherheit
+
+|:---|:---|:---|:---|
+| Ergebnis mit Kürzung auf Zielreifegrad  | ${averageMaturity(isacontrols, 3)?string["0.00"]}   | Höchstes erreichbares Ergebnis:| ${3.00?string["0.00"]}|
+{.table .fullwidth}
+
+
+## Details
+
+| Nr. | Thema | Ziel-Reifegrad | Ergebnis |
+|:---|:---|:---:|:---:|
+<#list isacontrols as control>
+| ${control.abbreviation} | ${control.name} | 3 | <@maturitydisplay control /> |
+</#list>
+{.table .fullwidth .maturitytable  .paginated}
+
+<#if prototypecontrols?has_content>
+
+<div class="pagebreak" />
+
+
+# Information Security Assessment <br/> Ergebnisse - Prototypenschutz
+
+|:---|:---|:---|:---|
+| Ergebnis mit Kürzung auf Zielreifegrad  | ${averageMaturity(prototypecontrols, 3)?string["0.00"]}   | Höchstes erreichbares Ergebnis:| ${3.00?string["0.00"]} |
+{.table .fullwidth}
+
+
+## Details
+
+| Nr. | Thema | Ziel-Reifegrad | Ergebnis |
+|:---|:---|:---:|:---:|
+<#list prototypecontrols as control>
+| ${control.abbreviation} | ${control.name} | 3 | <@maturitydisplay control /> |
+</#list>
+{.table .fullwidth .maturitytable .paginated}
+</#if>
+
+<#if prototypecontrols?has_content>
+
+<div class="pagebreak" />
+
+
+# Information Security Assessment <br/> Ergebnisse - Zusätzliche Anforderungen an den Datenschutz
+
+## Details
+
+| Nr. | Thema | Bewertung |
+|:---|:---|:---|
+<#list dataprotectioncontrols as control>
+| ${control.abbreviation} | ${control.name} | ${(bundle[control.control_isaDataProtection_assessment])!} |
+</#list>
+{.table .fullwidth}
+</#if>
\ No newline at end of file
diff --git a/src/main/resources/templates/tisax-compact.properties b/src/main/resources/templates/tisax-compact.properties
new file mode 100644
index 0000000..39ae05d
--- /dev/null
+++ b/src/main/resources/templates/tisax-compact.properties
@@ -0,0 +1,48 @@
+title=ISA: Information Security Assessment 6
+
+yes=Ja
+no=Nein
+
+creation_date=Erstelldatum
+
+isa_scope=Geltungsbereich
+
+main_page=Hauptblatt
+
+chapter_label_1=Richtlinien und Organisation der Informationssicherheit
+chapter_label_1.1=Richtlinien zur Informationssicherheit
+chapter_label_1.2=Organisation der Informationssicherheit
+chapter_label_1.3=Asset-Management
+chapter_label_1.4=Risikomanagement für Informationssicherheit
+chapter_label_1.5=Assessment
+chapter_label_1.6=Vorfall- und Krisenmanagement
+chapter_label_2=Personalabteilung
+chapter_label_2.1=Personal
+chapter_label_3=Physische Sicherheit
+chapter_label_3.1=Physische Sicherheit und Geschäftskontinuität
+chapter_label_4=Identitäts- und Zugriffsverwaltung
+chapter_label_4.1=Identitätsverwaltung
+chapter_label_4.2=Zugriffsverwaltung
+chapter_label_5=IT-Sicherheit/Cybersicherheit
+chapter_label_5.1=Kryptografie
+chapter_label_5.2=Operations Security
+chapter_label_5.3=Systemanschaffung, Anforderungsmanagement und Entwicklung
+chapter_label_6=Lieferantenbeziehungen
+chapter_label_6.1=Lieferantenbeziehungen
+chapter_label_7=Compliance
+chapter_label_7.1=Compliance
+chapter_label_8=Prototypenschutz
+chapter_label_8.1=Physische und umgebungsbezogene Sicherheit
+chapter_label_8.2=Organisatorische Anforderungen
+chapter_label_8.3=Umgang mit Fahrzeugen, Komponenten und Bauteilen
+chapter_label_8.4=Anforderungen für Versuchsfahrzeuge
+chapter_label_8.5=Anforderungen für Veranstaltungen und Shootings
+chapter_label_9=Data Protection Policies and Organization
+chapter_label_9.1=Data Protection Policies
+chapter_label_9.2=Organization of Data Protection
+chapter_label_9.3=Verarbeitungsverzeichnis
+chapter_label_9.4=Datenschutzfolgenabschützung
+chapter_label_9.5=Datenübermittlungen
+chapter_label_9.6=Umgang mit Anfragen und Vorfüllen
+chapter_label_9.7=Human Ressources
+chapter_label_9.8=Weisungen
\ No newline at end of file
diff --git a/src/test/groovy/org/veo/reporting/ReportEngineSpec.groovy b/src/test/groovy/org/veo/reporting/ReportEngineSpec.groovy
index 7317026..d8c9439 100644
--- a/src/test/groovy/org/veo/reporting/ReportEngineSpec.groovy
+++ b/src/test/groovy/org/veo/reporting/ReportEngineSpec.groovy
@@ -202,7 +202,8 @@ Cheers'''
             'itbp-a4',
             'itbp-a5',
             'itbp-a6',
-            'nis2-registration-info'
+            'nis2-registration-info',
+            'tisax-compact'
         ]
     }
 
diff --git a/src/test/groovy/org/veo/reporting/controllers/ReportControllerSpec.groovy b/src/test/groovy/org/veo/reporting/controllers/ReportControllerSpec.groovy
index 62d6ab3..f15795b 100644
--- a/src/test/groovy/org/veo/reporting/controllers/ReportControllerSpec.groovy
+++ b/src/test/groovy/org/veo/reporting/controllers/ReportControllerSpec.groovy
@@ -79,7 +79,8 @@ public class ReportControllerSpec extends ReportingTest {
             'itbp-a4',
             'itbp-a5',
             'itbp-a6',
-            'nis2-registration-info'
+            'nis2-registration-info',
+            'tisax-compact'
         ]
     }