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' ] }