obligations-document, tom-document, loeschfristen-document, compliance-scope-triggers, sdk-flow/flow-data, processing-activities, loeschfristen-baseline-catalog, catalog-registry, dsfa mitigation-library + risk-catalog, vvt-baseline-catalog, vendor contract-review checklists + findings, demo-data, tom-compliance. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
307 lines
14 KiB
TypeScript
307 lines
14 KiB
TypeScript
// =============================================================================
|
||
// TOM Document — HTML Sections 1–6
|
||
// =============================================================================
|
||
|
||
import type { DerivedTOM, CompanyProfile, RiskProfile, ControlCategory } from '../tom-generator/types'
|
||
import { getControlById, getAllCategories, getCategoryMetadata } from '../tom-generator/controls/loader'
|
||
import type { TOMDocumentOrgHeader } from './types-defaults'
|
||
import {
|
||
CATEGORY_LABELS_DE,
|
||
STATUS_LABELS_DE,
|
||
STATUS_BADGE_CLASSES,
|
||
APPLICABILITY_LABELS_DE,
|
||
} from './types-defaults'
|
||
import { escHtml, formatDateDE } from './helpers'
|
||
|
||
export function buildSections1to6(
|
||
orgName: string,
|
||
orgHeader: TOMDocumentOrgHeader,
|
||
companyProfile: CompanyProfile | null,
|
||
riskProfile: RiskProfile | null,
|
||
applicableTOMs: DerivedTOM[],
|
||
tomsByCategory: Map<ControlCategory, DerivedTOM[]>,
|
||
today: string
|
||
): string {
|
||
let html = ''
|
||
html += buildCoverAndTOC(orgName, orgHeader, applicableTOMs, tomsByCategory, today)
|
||
html += buildSection1(orgName)
|
||
html += buildSection2(orgName, orgHeader, companyProfile, applicableTOMs, tomsByCategory)
|
||
html += buildSection3()
|
||
html += buildSection4(riskProfile)
|
||
html += buildSection5(tomsByCategory)
|
||
html += buildSection6(tomsByCategory)
|
||
return html
|
||
}
|
||
|
||
function buildCoverAndTOC(
|
||
orgName: string,
|
||
orgHeader: TOMDocumentOrgHeader,
|
||
applicableTOMs: DerivedTOM[],
|
||
tomsByCategory: Map<ControlCategory, DerivedTOM[]>,
|
||
today: string
|
||
): string {
|
||
const sections = [
|
||
'Ziel und Zweck',
|
||
'Geltungsbereich',
|
||
'Grundprinzipien Art. 32',
|
||
'Schutzbedarf und Risikoanalyse',
|
||
'Massnahmen-Uebersicht',
|
||
'Detaillierte Massnahmen',
|
||
'SDM Gewaehrleistungsziele',
|
||
'Verantwortlichkeiten',
|
||
'Pruef- und Revisionszyklus',
|
||
'Compliance-Status',
|
||
'Aenderungshistorie',
|
||
]
|
||
|
||
return `
|
||
<div class="cover">
|
||
<h1>TOM-Dokumentation</h1>
|
||
<div class="subtitle">Technische und Organisatorische Massnahmen gemaess Art. 32 DSGVO</div>
|
||
<div class="org-info">
|
||
<div><span class="label">Organisation:</span> ${escHtml(orgName)}</div>
|
||
${orgHeader.industry ? `<div><span class="label">Branche:</span> ${escHtml(orgHeader.industry)}</div>` : ''}
|
||
${orgHeader.dpoName ? `<div><span class="label">DSB:</span> ${escHtml(orgHeader.dpoName)}</div>` : ''}
|
||
${orgHeader.dpoContact ? `<div><span class="label">DSB-Kontakt:</span> ${escHtml(orgHeader.dpoContact)}</div>` : ''}
|
||
${orgHeader.responsiblePerson ? `<div><span class="label">Verantwortlicher:</span> ${escHtml(orgHeader.responsiblePerson)}</div>` : ''}
|
||
${orgHeader.itSecurityContact ? `<div><span class="label">IT-Sicherheit:</span> ${escHtml(orgHeader.itSecurityContact)}</div>` : ''}
|
||
${orgHeader.employeeCount ? `<div><span class="label">Mitarbeiter:</span> ${escHtml(orgHeader.employeeCount)}</div>` : ''}
|
||
${orgHeader.locations.length > 0 ? `<div><span class="label">Standorte:</span> ${escHtml(orgHeader.locations.join(', '))}</div>` : ''}
|
||
</div>
|
||
<div class="legal-ref">
|
||
Version ${escHtml(orgHeader.documentVersion)} | Stand: ${today}<br/>
|
||
Letzte Pruefung: ${formatDateDE(orgHeader.lastReviewDate)} | Naechste Pruefung: ${formatDateDE(orgHeader.nextReviewDate)}<br/>
|
||
Pruefintervall: ${escHtml(orgHeader.reviewInterval)}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toc">
|
||
<h2>Inhaltsverzeichnis</h2>
|
||
${sections.map((s, i) => `<div class="toc-entry"><span><span class="toc-num">${i + 1}.</span> ${escHtml(s)}</span></div>`).join('\n ')}
|
||
</div>
|
||
`
|
||
}
|
||
|
||
function buildSection1(orgName: string): string {
|
||
return `
|
||
<div class="section">
|
||
<div class="section-header">1. Ziel und Zweck</div>
|
||
<div class="section-body">
|
||
<p>Diese TOM-Dokumentation beschreibt die technischen und organisatorischen Massnahmen
|
||
zum Schutz personenbezogener Daten bei <strong>${escHtml(orgName)}</strong>. Sie dient
|
||
der Umsetzung folgender DSGVO-Anforderungen:</p>
|
||
<table>
|
||
<tr><th>Rechtsgrundlage</th><th>Inhalt</th></tr>
|
||
<tr><td><strong>Art. 32 Abs. 1 lit. a DSGVO</strong></td><td>Pseudonymisierung und Verschluesselung personenbezogener Daten</td></tr>
|
||
<tr><td><strong>Art. 32 Abs. 1 lit. b DSGVO</strong></td><td>Faehigkeit, die Vertraulichkeit, Integritaet, Verfuegbarkeit und Belastbarkeit der Systeme und Dienste im Zusammenhang mit der Verarbeitung auf Dauer sicherzustellen</td></tr>
|
||
<tr><td><strong>Art. 32 Abs. 1 lit. c DSGVO</strong></td><td>Faehigkeit, die Verfuegbarkeit der personenbezogenen Daten und den Zugang zu ihnen bei einem physischen oder technischen Zwischenfall rasch wiederherzustellen</td></tr>
|
||
<tr><td><strong>Art. 32 Abs. 1 lit. d DSGVO</strong></td><td>Verfahren zur regelmaessigen Ueberpruefung, Bewertung und Evaluierung der Wirksamkeit der technischen und organisatorischen Massnahmen</td></tr>
|
||
</table>
|
||
<p>Die TOM-Dokumentation ist fester Bestandteil des Datenschutz-Managementsystems und wird
|
||
regelmaessig ueberprueft und aktualisiert.</p>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
function buildSection2(
|
||
orgName: string,
|
||
orgHeader: TOMDocumentOrgHeader,
|
||
companyProfile: CompanyProfile | null,
|
||
applicableTOMs: DerivedTOM[],
|
||
tomsByCategory: Map<ControlCategory, DerivedTOM[]>
|
||
): string {
|
||
const industryInfo = companyProfile?.industry || orgHeader.industry || ''
|
||
const hostingInfo = companyProfile
|
||
? `Unternehmen: ${escHtml(companyProfile.name || orgName)}, Groesse: ${escHtml(companyProfile.size || '-')}`
|
||
: ''
|
||
|
||
return `
|
||
<div class="section">
|
||
<div class="section-header">2. Geltungsbereich</div>
|
||
<div class="section-body">
|
||
<p>Diese TOM-Dokumentation gilt fuer alle IT-Systeme, Anwendungen und Verarbeitungsprozesse
|
||
von <strong>${escHtml(orgName)}</strong>${industryInfo ? ` (Branche: ${escHtml(industryInfo)})` : ''}.</p>
|
||
${hostingInfo ? `<p>${hostingInfo}</p>` : ''}
|
||
${orgHeader.locations.length > 0 ? `<p>Standorte: ${escHtml(orgHeader.locations.join(', '))}</p>` : ''}
|
||
<p>Die dokumentierten Massnahmen stammen aus zwei Quellen:</p>
|
||
<ul style="margin: 8px 0 8px 24px;">
|
||
<li><strong>Embedded Library (TOM-xxx):</strong> Integrierte Kontrollbibliothek mit spezifischen Massnahmen fuer Art. 32 DSGVO</li>
|
||
<li><strong>Canonical Control Library (CP-CLIB):</strong> Uebergreifende Kontrollbibliothek mit framework-uebergreifenden Massnahmen</li>
|
||
</ul>
|
||
<p>Insgesamt umfasst dieses Dokument <strong>${applicableTOMs.length}</strong> anwendbare Massnahmen
|
||
in <strong>${tomsByCategory.size}</strong> Kategorien.</p>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
function buildSection3(): string {
|
||
return `
|
||
<div class="section">
|
||
<div class="section-header">3. Grundprinzipien Art. 32</div>
|
||
<div class="section-body">
|
||
<div class="principle"><strong>Vertraulichkeit:</strong> Schutz personenbezogener Daten vor unbefugter Kenntnisnahme durch Zutrittskontrolle, Zugangskontrolle, Zugriffskontrolle und Verschluesselung (Art. 32 Abs. 1 lit. b DSGVO).</div>
|
||
<div class="principle"><strong>Integritaet:</strong> Sicherstellung, dass personenbezogene Daten nicht unbefugt oder unbeabsichtigt veraendert werden koennen, durch Eingabekontrolle, Weitergabekontrolle und Protokollierung (Art. 32 Abs. 1 lit. b DSGVO).</div>
|
||
<div class="principle"><strong>Verfuegbarkeit und Belastbarkeit:</strong> Gewaehrleistung, dass Systeme und Dienste bei Lastspitzen und Stoerungen zuverlaessig funktionieren, durch Backup, Redundanz und Disaster Recovery (Art. 32 Abs. 1 lit. b DSGVO).</div>
|
||
<div class="principle"><strong>Rasche Wiederherstellbarkeit:</strong> Faehigkeit, nach einem physischen oder technischen Zwischenfall Daten und Systeme schnell wiederherzustellen, durch getestete Recovery-Prozesse (Art. 32 Abs. 1 lit. c DSGVO).</div>
|
||
<div class="principle"><strong>Regelmaessige Wirksamkeitspruefung:</strong> Verfahren zur regelmaessigen Ueberpruefung, Bewertung und Evaluierung der Wirksamkeit aller technischen und organisatorischen Massnahmen (Art. 32 Abs. 1 lit. d DSGVO).</div>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
|
||
function buildSection4(riskProfile: RiskProfile | null): string {
|
||
let html = `
|
||
<div class="section">
|
||
<div class="section-header">4. Schutzbedarf und Risikoanalyse</div>
|
||
<div class="section-body">
|
||
`
|
||
if (riskProfile) {
|
||
html += ` <p>Die folgende Schutzbedarfsanalyse bildet die Grundlage fuer die Auswahl und Priorisierung
|
||
der technischen und organisatorischen Massnahmen:</p>
|
||
<table>
|
||
<tr><th>Kriterium</th><th>Bewertung</th></tr>
|
||
<tr><td>Vertraulichkeit</td><td>${riskProfile.ciaAssessment.confidentiality}/5</td></tr>
|
||
<tr><td>Integritaet</td><td>${riskProfile.ciaAssessment.integrity}/5</td></tr>
|
||
<tr><td>Verfuegbarkeit</td><td>${riskProfile.ciaAssessment.availability}/5</td></tr>
|
||
<tr><td>Schutzniveau</td><td><strong>${escHtml(riskProfile.protectionLevel)}</strong></td></tr>
|
||
<tr><td>DSFA-Pflicht</td><td>${riskProfile.dsfaRequired ? 'Ja' : 'Nein'}</td></tr>
|
||
${riskProfile.specialRisks.length > 0 ? `<tr><td>Spezialrisiken</td><td>${escHtml(riskProfile.specialRisks.join(', '))}</td></tr>` : ''}
|
||
${riskProfile.regulatoryRequirements.length > 0 ? `<tr><td>Regulatorische Anforderungen</td><td>${escHtml(riskProfile.regulatoryRequirements.join(', '))}</td></tr>` : ''}
|
||
</table>
|
||
`
|
||
} else {
|
||
html += ` <p><em>Die Schutzbedarfsanalyse wurde noch nicht durchgefuehrt. Fuehren Sie den
|
||
Risiko-Wizard im TOM-Generator durch, um den Schutzbedarf zu ermitteln.</em></p>
|
||
`
|
||
}
|
||
html += ` </div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|
||
|
||
function buildSection5(tomsByCategory: Map<ControlCategory, DerivedTOM[]>): string {
|
||
const allCategories = getAllCategories()
|
||
|
||
let html = `
|
||
<div class="section page-break">
|
||
<div class="section-header">5. Massnahmen-Uebersicht</div>
|
||
<div class="section-body">
|
||
<p>Die folgende Tabelle zeigt eine Uebersicht aller anwendbaren Massnahmen nach Kategorie:</p>
|
||
<table>
|
||
<tr>
|
||
<th>Kategorie</th>
|
||
<th>Gesamt</th>
|
||
<th>Umgesetzt</th>
|
||
<th>Teilweise</th>
|
||
<th>Offen</th>
|
||
</tr>
|
||
`
|
||
for (const cat of allCategories) {
|
||
const tomsInCat = tomsByCategory.get(cat)
|
||
if (!tomsInCat || tomsInCat.length === 0) continue
|
||
|
||
const implemented = tomsInCat.filter(t => t.implementationStatus === 'IMPLEMENTED').length
|
||
const partial = tomsInCat.filter(t => t.implementationStatus === 'PARTIAL').length
|
||
const notImpl = tomsInCat.filter(t => t.implementationStatus === 'NOT_IMPLEMENTED').length
|
||
const catLabel = CATEGORY_LABELS_DE[cat] || cat
|
||
|
||
html += ` <tr>
|
||
<td>${escHtml(catLabel)}</td>
|
||
<td>${tomsInCat.length}</td>
|
||
<td>${implemented}</td>
|
||
<td>${partial}</td>
|
||
<td>${notImpl}</td>
|
||
</tr>
|
||
`
|
||
}
|
||
|
||
html += ` </table>
|
||
</div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|
||
|
||
function buildSection6(tomsByCategory: Map<ControlCategory, DerivedTOM[]>): string {
|
||
const allCategories = getAllCategories()
|
||
|
||
let html = `
|
||
<div class="section">
|
||
<div class="section-header">6. Detaillierte Massnahmen</div>
|
||
<div class="section-body">
|
||
`
|
||
|
||
for (const cat of allCategories) {
|
||
const tomsInCat = tomsByCategory.get(cat)
|
||
if (!tomsInCat || tomsInCat.length === 0) continue
|
||
|
||
const catLabel = CATEGORY_LABELS_DE[cat] || cat
|
||
const catMeta = getCategoryMetadata(cat)
|
||
const gdprRef = catMeta?.gdprReference || ''
|
||
|
||
html += ` <h3 style="color: #5b21b6; margin: 20px 0 10px 0; font-size: 11pt;">${escHtml(catLabel)}${gdprRef ? ` <span style="font-weight: 400; font-size: 9pt; color: #64748b;">(${escHtml(gdprRef)})</span>` : ''}</h3>
|
||
`
|
||
|
||
const sortedTOMs = [...tomsInCat].sort((a, b) => {
|
||
const codeA = getControlById(a.controlId)?.code || a.controlId
|
||
const codeB = getControlById(b.controlId)?.code || b.controlId
|
||
return codeA.localeCompare(codeB)
|
||
})
|
||
|
||
for (const tom of sortedTOMs) {
|
||
const control = getControlById(tom.controlId)
|
||
const code = control?.code || tom.controlId
|
||
const nameDE = control?.name?.de || tom.name
|
||
const descDE = control?.description?.de || tom.description
|
||
const typeLabel = control?.type === 'TECHNICAL' ? 'Technisch' : control?.type === 'ORGANIZATIONAL' ? 'Organisatorisch' : '-'
|
||
const statusLabel = STATUS_LABELS_DE[tom.implementationStatus] || tom.implementationStatus
|
||
const statusBadge = STATUS_BADGE_CLASSES[tom.implementationStatus] || 'badge-draft'
|
||
const applicabilityLabel = APPLICABILITY_LABELS_DE[tom.applicability] || tom.applicability
|
||
const responsible = [tom.responsiblePerson, tom.responsibleDepartment].filter(s => s && s.trim()).join(' / ') || '-'
|
||
const implDate = tom.implementationDate ? formatDateDE(typeof tom.implementationDate === 'string' ? tom.implementationDate : tom.implementationDate.toISOString()) : '-'
|
||
const reviewDate = tom.reviewDate ? formatDateDE(typeof tom.reviewDate === 'string' ? tom.reviewDate : tom.reviewDate.toISOString()) : '-'
|
||
|
||
const evidenceInfo = tom.linkedEvidence.length > 0
|
||
? tom.linkedEvidence.join(', ')
|
||
: tom.evidenceGaps.length > 0
|
||
? `<em style="color: #d97706;">Fehlend: ${escHtml(tom.evidenceGaps.join(', '))}</em>`
|
||
: '-'
|
||
|
||
let mappingsHtml = '-'
|
||
if (control?.mappings && control.mappings.length > 0) {
|
||
mappingsHtml = control.mappings.map(m => `${escHtml(m.framework)}: ${escHtml(m.reference)}`).join('<br/>')
|
||
}
|
||
|
||
html += `
|
||
<div class="policy-detail">
|
||
<div class="policy-detail-header">
|
||
<span>${escHtml(code)} — ${escHtml(nameDE)}</span>
|
||
<span class="badge ${statusBadge}">${escHtml(statusLabel)}</span>
|
||
</div>
|
||
<div class="policy-detail-body">
|
||
<table>
|
||
<tr><th>Beschreibung</th><td>${escHtml(descDE)}</td></tr>
|
||
<tr><th>Massnahmentyp</th><td>${escHtml(typeLabel)}</td></tr>
|
||
<tr><th>Anwendbarkeit</th><td>${escHtml(applicabilityLabel)}${tom.applicabilityReason ? ` — ${escHtml(tom.applicabilityReason)}` : ''}</td></tr>
|
||
<tr><th>Umsetzungsstatus</th><td><span class="badge ${statusBadge}">${escHtml(statusLabel)}</span></td></tr>
|
||
<tr><th>Verantwortlich</th><td>${escHtml(responsible)}</td></tr>
|
||
<tr><th>Umsetzungsdatum</th><td>${implDate}</td></tr>
|
||
<tr><th>Naechste Pruefung</th><td>${reviewDate}</td></tr>
|
||
<tr><th>Evidence</th><td>${evidenceInfo}</td></tr>
|
||
<tr><th>Framework-Mappings</th><td>${mappingsHtml}</td></tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
`
|
||
}
|
||
}
|
||
|
||
html += ` </div>
|
||
</div>
|
||
`
|
||
return html
|
||
}
|