15 Datenschutz
Dieses Kapitel beschreibt im Einzelnen, welche personenbezogenen Daten camindo CMS speichert, wo sie liegen und wie lange sie bleiben – getrennt nach Benutzern (Ihren Mitarbeitern im Backend), Website-Usern (Frontend-Community) und Besuchern ohne Konto. Es ist die Grundlage für Ihr Verzeichnis der Verarbeitungstätigkeiten und Ihre Datenschutzerklärung. Die Übersicht für Entscheider steht in Kapitel 14.
- Benutzer (Mitarbeiter)
- Website-User (Community)
- Besucher ohne Konto
- IP-Adressen
- Löschen nach Lösch-Policy
- Datenexport personenbezogener Daten
- Was außerhalb von camindo CMS liegt
- Checkliste
Alle Fristen sind Vorgaben; die Schlüssel stehen in .camindo.php (siehe
Kapitel 12). Gelöschte Daten leben in
Datenbanksicherungen weiter, bis die Sicherung selbst ausläuft – das gehört in
Ihr Löschkonzept.
Benutzer (Mitarbeiter)
| Daten | Wo | Wie lange | |
|---|---|---|---|
| Unverschlüsselte Benutzerdaten | Anmeldename, Namen (Vor-, Anzeige-, Sortiername), Benutzergruppe, Status (freigeschaltet/gesperrt/gelöscht) | Tabelle admin |
solange das Konto besteht; gelöschte Benutzer bleiben erhalten, damit „erstellt/geändert von“ in Inhalten und im Audit-Log gültig bleibt – bis sie anonymisiert werden |
| Verschlüsselte Benutzerdaten | E-Mail, Mobil, Telefon | admin, verschlüsselt |
wie oben |
| Passwort | Hash (nicht umkehrbar) Tag der letzten Änderung (ohne Uhrzeit) |
admin |
wie oben |
| 2FA-Daten | Authenticator-App, Passkeys (Bezeichnung, Zeitpunkt), Wiederherstellungscodes | admin (Schlüssel verschlüsselt), admin_passkey, admin_recovery_code (nur Hashes) |
bis zum Entfernen; Passkeys und Wiederherstellungscodes mit dem Löschen des Kontos; der Schlüssel der App beim Anonymisieren |
| Persönliche Einstellungen | Sprache, Zeitzone, Widgets, Berichte … | admin.settings |
solange das Konto besteht |
| Sitzungsdaten | Oberfläche, letzter Zugriff | admin_session |
bis zur Abmeldung bzw. admin.max_inactive_sec (30 Minuten) Untätigkeit; mit dem Löschen des Kontos sofort |
| Bekannte Geräte | Browser/Betriebssystem, gekürzte IP-Adresse, Zeitpunkt der ersten und letzten Anmeldung | login_device |
security.device_days (180 Tage) nach der letzten Nutzung; mit dem Löschen des Kontos sofort |
| Letzte Anmeldung | Tag (ohne Uhrzeit) | admin.login_date |
wird bei jeder Anmeldung überschrieben |
| Laufende Anmeldung | Zeitpunkt, zu dem die Zwei-Faktor-Anmeldung begann (Code-Versand) | admin.uid_date |
nur bis zum Abschluss der Anmeldung, danach geleert |
| Aktivitäten | was der Benutzer wann geändert hat, mit IP-Adresse | Audit-Log: audit_log (Metadaten), var/audit/ (Inhalte) |
audit.retention_days_changes (unbegrenzt); Einträge zu Datensätzen nach der Lösch-Policy ihres Bereichs; die IP-Adresse wird nach privacy.ip_anonymize_days (7 Tage) gekürzt |
| Fehlgeschlagene Anmeldungen (Audit-Log) | Nummer bzw. eingegebener Name, IP-Adresse, Zeitpunkt, Oberfläche (Backend, PHP-API, Kommandozeile) (bei erkanntem Konto unter dessen Nummer, sonst mit dem eingegebenen Namen) |
Audit-Log | audit.retention_days_logins (90 Tage) |
| „Erstellt von“, „Geändert von“, Owner |
Zeitpunkte | Tabelle record |
solange der Datensatz besteht |
| Historie | Zuletzt geöffnete Datensätze und Templates, Zeitpunkt | history |
nur die jeweils jüngsten Einträge, ältere fallen automatisch weg; mit dem Löschen des Kontos alle |
| Interne Nachrichten | Absender, Empfänger, Betreff, Text verschlüsselt, Zeitpunkt | message |
privacy.message_days (60 Tage) nach dem Versand – für beide Seiten, auch ungelesen; vorher, sobald Absender und Empfänger sie gelöscht haben |
| Erfolgreiche An- und Abmeldungen | Zeitpunkt, IP-Adresse, Oberfläche | Audit-Log – nur mit audit.log_logins = true |
Vorgabe: nicht aufgezeichnet; eingeschaltet audit.retention_days_logins (90 Tage) |
| Fehlgeschlagene Anmeldungen (Sperre) | Zeitpunkt, eingegebener Name, IP-Adresse | admin_login_attempt; beim Sperren eine Zeile im Log security |
in der Tabelle nur für die Sperrdauer admin.bruteforce_delay (10 Minuten), im Log nach der Aufgabe „Logs aufräumen“; IP-Adresse gekürzt nach privacy.ip_anonymize_days |
| Hinweis-E-Mails | Neues Gerät, Passwort geändert – mit voller IP-Adresse | Postfach des Benutzers | nicht in camindo CMS gespeichert |
Benutzernamen, die mit admin_ beginnen, sind für anonymisierte Konten reserviert und
lassen sich nicht neu vergeben. Den Datenexport eines Benutzers erstellt, wer im Modul
„Datenschutz“ das Recht dazu hat (siehe Datenexport).
Fehlgeschlagene Anmeldungen stehen immer im Audit-Log, nach mehreren Fehlversuchen
zusätzlich im Log security. Beides wird nach audit.retention_days_logins gelöscht;
im Datenexport steht bei jedem Eintrag der Tag seiner Löschung.
Anmeldezeiten: Reine Anmeldungen ohne Tätigkeit sagen nichts über Änderungen,
wohl aber über die Arbeitszeiten Ihrer Mitarbeiter. camindo CMS zeichnet erfolgreiche
An- und Abmeldungen deshalb nur auf, wenn Sie audit.log_logins einschalten;
die Standardeinstellung ist false.
Was Sie als Arbeitgeber beachten sollten: Das Audit-Log zeigt, wer wann was geändert hat. Das ist für Nachvollziehbarkeit und Sicherheit gedacht, kann aber zur Leistungs- und Verhaltenskontrolle genutzt werden. Regeln Sie Zweck und Zugriff (Recherche nur für Superuser) in einer Betriebsvereinbarung bzw. internen Richtlinie und informieren Sie Ihre Mitarbeiter.
Ausgeschiedener Mitarbeiter
Ein Benutzerkonto besteht nach dem Löschen weiter, taucht aber nicht mehr in der Benutzerliste auf. Anmeldung ist nicht mehr möglich. Zweck: Nachvollziehbarkeit – Datensätze, Templates und das Audit-Log verweisen auf ihn.
Ein Superuser kann die Nachrichten eines „gelöschten“ Benutzers löschen: Benutzer
öffnen → Reiter „Einstellungen“ → Interne Nachrichten → „Alle Nachrichten
löschen“. Empfangene Nachrichten werden endgültig gelöscht; gesendete verschwinden
aus seinem Postausgang, die Empfänger behalten ihre Fassung. Der Vorgang steht im
Log security. Geräte und Sitzungen entfernen Sie im Reiter „Geräte und Sitzungen“.
Gelöschte Benutzer anonymisieren
Sobald diese Nachvollziehbarkeit nicht mehr gebraucht wird (oder der ehemalige Mitarbeiter die Löschung verlangt), entfernen Sie die Person mit Anonymisieren: Modul „Datenschutz“ → Reiter „Gelöschte Benutzer“ → Benutzer anklicken → auf seiner Infoseite „Anonymisieren“. Die Schaltfläche sehen Superuser und Benutzergruppen mit dem Zusatzrecht „Gelöschte Benutzer anonymisieren“ (für Benutzer der Gruppen, die sie im Modul „Benutzer“ sehen).
| Daten | Anonymisierte Form |
|---|---|
| Benutzername | admin_{Id}, etwa admin_42 – überall, wo der Name erscheint, auch bei „erstellt/geändert von“ |
| Vor-, Anzeige- und Sortiername, E-Mail, Telefon, Mobil, persönliche Einstellungen | gelöscht |
| Passwort, Authenticator-App, Passkeys, Wiederherstellungscodes, Sitzungen, Geräte | gelöscht – das Konto kann sich nicht mehr anmelden |
| Audit-Log: Einträge, in denen er gehandelt hat | erscheinen mit admin_{Id} – das Audit-Log speichert Handelnde nur als Nummer und zeigt den heutigen Namen; die Änderungen selbst bleiben |
| Audit-Log seines Kontos | Bezeichnung admin_{Id}, die Werte der persönlichen Felder in Vorher/Nachher gelöscht |
| Fehlgeschlagene Anmeldungen unter seinem Namen | admin_{Id} statt des Namens |
Weil das Audit-Log Handelnde nur als Nummer speichert, dauert das Anonymisieren auch bei
Benutzern mit langer Vorgeschichte nur einen Augenblick. Bricht es dennoch ab, starten
Sie es einfach erneut – erst der Name admin_{Id} zeigt, dass es abgeschlossen ist.
Das lässt sich nicht rückgängig machen. Der Vorgang steht im Audit-Log des Kontos
(„Datenschutz“) und im Log security. Die Liste zeigt anonymisierte Benutzer mit dem
Hinweis „anonymisiert“.
Website-User (Community)
| Daten | Wo | Wie lange | |
|---|---|---|---|
| Persönliche Daten verschlüsselt | Alle Felder des Profils, die als verschlüsselte Felder eingerichtet wurden (i.d.R. alle mit persönlichen Daten, inkl. Benutzername, E-Mail-Adresse) | Profil-Bereich (Tabellen record, data_enc) |
bis zum Löschen des Profils, danach nach seiner Lösch-Policy – mit der Standard-Policy: admin.trash_retention_days (7 Tage) im Papierkorb, danach endgültig samt Dateien |
| Persönliche Daten unverschlüsselt | Alle Felder des Profils, die als unverschlüsselte Felder eingerichtet wurden, Zeitpunkte der Erstellung und letzten Änderung | Profil-Bereich (Tabellen record, data_*), media/ |
wie oben |
| Passwort | Hash | Profil-Bereich (Tabellen record, data_pwd) |
wie oben |
| Beiträge | Kommentare, Bewertungen … und hochgeladene Dateien, Zeitpunkte der Erstellung und letzten Änderung | Beitrags-Bereiche (Tabellen record, data_*), media/ |
nach der Lösch-Policy des Beitrags-Bereichs; mit den Standard-Policies wie das Profil, sobald es gelöscht wird |
| Datensätze, die auf das Profil verweisen | Verknüpfungen | jeweiliger Bereich | nach Ihren eigenen Regeln (etwa handels- und steuerrechtliche Fristen) |
| Bekannte Geräte | Gerät, gekürzte IP-Adresse, Zeitpunkt der ersten und letzten Anmeldung | login_device |
security.device_days (180 Tage) nach letzter Nutzung; mit dem Löschen des Profils sofort, gleich nach welcher Policy |
| Sitzungsdaten | Session-ID, letzter Zugriff | user_session |
user.max_inactive_sec (24 Stunden) Untätigkeit; mit dem Löschen des Profils sofort |
| 2FA-Daten | Art, Zeitpunkt der ersten Verwendung | user_second_factor |
bis zum Entfernen bzw. Löschen des Profils |
| Offene Bestätigungs- und Passwort-Links | Token, Zeitpunkt | user_token |
bis zur Nutzung bzw. zum Ablauf (user.token_expiration, user.password_token_expiration) |
| Nie bestätigte Registrierungen | Angaben aus dem Registrierungsprozess, Zeitpunkt | Profil-Bereich | user.unconfirmed_days (7 Tage) |
| Warenkorb | Datensatz-Ids (Produkte, Abos, …), Zeitpunkt der letzten Änderung | user_basket |
angemeldet: mit der Sitzung; anonym: user.basket_days (30 Tage) |
| Änderungsprotokoll | Audit-Log zum Profildatensatz und Beiträgen, mit IP-Adresse, Zeitpunkt | Audit-Log | nach der Lösch-Policy; mit den Standard-Policies beim Profil nach 7 Tagen alle Felder geleert, nach 1 Jahr gelöscht. Der User steht nur mit der Nummer seines Profils darin (anonym, sobald das Profil gelöscht ist). IP-Adresse gekürzt nach privacy.ip_anonymize_days |
| Fehlgeschlagene Anmeldungen | Anmeldename, Zeitpunkt, IP nur mit user.bruteforce_ip |
user_login_attempt |
für die Sperrdauer user.bruteforce_delay |
Anmeldenamen, die mit user_ beginnen, sind für anonymisierte Profile reserviert:
bei der Registrierung und im Backend abgelehnt – erlaubt ist nur user_{record_id}
des Profils selbst, wie es eine Lösch-Policy einsetzt.
Den Datenexport eines Website-Users erstellt, wer im Modul „Datenschutz“ das Recht dazu hat (siehe Datenexport und das Handbuch für Anwender).
Besucher ohne Konto
camindo CMS setzt im Frontend nur technisch notwendige Cookies (Sitzung, Frontend-Editing, Gerätewiedererkennung bei der Anmeldung) und bindet keine Dienste Dritter ein. Was Ihre Templates zusätzlich laden (Schriften, Karten, Statistik), liegt in Ihrer Verantwortung.
| Daten | Wo | Wie lange | |
|---|---|---|---|
| Drosselung (Formulare, Anmeldung, CSP-Berichte) |
IP-Adressen nur als Hash, nie im Klartext | Tabelle des RateLimiters | wenige Minuten bis Stunden (Zeitfenster der Drosselung) |
| Gesperrte Anmeldungen, abgewiesene Formulare |
Anmeldename, IP-Adresse, Zeitpunkt | Log security in var/log/ |
Aufgabe „Logs aufräumen“ (Vorgabe 365 Tage); IP-Adressen gekürzt nach privacy.ip_anonymize_days |
| Script-Verstöße (CSP-Berichte) | Script-URL, Zeitpunkt, (keine IP-Adresse) | Log csp |
wie oben |
| Formulareingaben | Teils persönliche Daten | je nach Template: als Datensatz oder E-Mail, je nach Bereich verschlüsselt / unverschlüsselt | nach den Lösch-Regeln des Bereichs, bzw. Postfachs |
IP-Adressen
IP-Adressen sind personenbezogene Daten. Das berechtigte Interesse an der vollständigen Adresse – Angriffe erkennen und abwehren – trägt nach verbreiteter Auffassung nur wenige Tage. camindo CMS hält es deshalb so:
| Wo | Was gespeichert wird | |
|---|---|---|
| Bekannte Geräte | login_device |
von Anfang an nur die gekürzte Adresse |
| Inhalte des Audit-Logs | var/audit/ |
von Anfang an nur die gekürzte Adresse – die Dateien sind fälschungsgeschützt und werden nur ergänzt, nachträglich kürzen ginge nicht |
| Metadaten des Audit-Logs | audit_log |
die volle Adresse, gekürzt nach privacy.ip_anonymize_days (7 Tage) |
| Fehlgeschlagene Anmeldungen | admin_login_attempt, user_login_attempt |
die volle Adresse für die Sperrdauer; gekürzt nach derselben Frist, falls sie länger stehen |
| Logdateien | var/log/ |
die volle Adresse; jede Logdatei, deren Tag älter ist als die Frist, wird gekürzt |
| Drosselung | RateLimiter-Tabelle | nur ein Hash, nie die Adresse |
Welche Adresse „die des Besuchers“ ist, bestimmt an allen Stellen dieselbe Regel:
REMOTE_ADDR, hinter einem eingetragenen Proxy (security.trusted_proxies) die
Adresse aus X-Forwarded-For. Ohne diesen Eintrag steht hinter einem Proxy überall die
Adresse des Proxys.
Wie gekürzt wird:
- IPv4: das letzte Oktett wird genullt (
203.0.113.45→203.0.113.0), mitprivacy.ip_v4_octets = 2die letzten beiden (203.0.0.0). - IPv6: die letzten 80 Bit werden genullt, es bleiben die ersten 48 Bit – das
Netz des Anbieters, nicht der einzelne Anschluss
(
2001:db8:abcd:12::1→2001:db8:abcd::).
Einstellung später ändern: Erhöhen Sie privacy.ip_v4_octets von 1 auf 2, kürzt
der nächste Lauf der Aufgabe „Aufbewahrungsfristen anwenden“ alle bereits gekürzten
Adressen weiter – in der Datenbank, den Logdateien und den Inhalten des Audit-Logs
(203.0.113.0 → 203.0.0.0, angezeigt als 203.0.xxx.xxx). Senken bringt nichts
zurück: Was einmal genullt ist, bleibt es; die Anzeige markiert weiterhin beide Stellen.
Angezeigt werden die gekürzten Stellen als xxx – in der Datenauskunft, in der
Recherche und im Reiter „Änderungen“ des Audit-Logs und in der Geräteliste
(203.0.113.xxx, 2001:db8:abcd:xxxx:xxxx:xxxx:xxxx:xxxx). So hält niemand die Null
für einen Teil der Adresse. Gespeichert bleibt die Adresse mit Nullen: nur so ist sie
weiterhin eine gültige IP-Adresse, die sich vergleichen und – nach einer Änderung von
privacy.ip_v4_octets – weiter kürzen lässt.
Das Kürzen erledigt die Zeitplaner-Aufgabe „Aufbewahrungsfristen anwenden“
(von der Installation eingerichtet, jede Nacht). Jeder Lauf nimmt nur die Einträge
seit dem vorigen Lauf und nutzt die Indizes der Zeitspalten. Logdateien werden
zeilenweise umgeschrieben; das hält es auch bei großen Datenbeständen schnell – unter
Linux wie unter Windows. Der erste Lauf nach dem Update geht einmal über den ganzen
Bestand, ebenso nach einer Änderung von privacy.ip_v4_octets.
Einmalig nach dem Update: Einträge in var/audit/, die vor diesem Update
geschrieben wurden, enthalten noch die volle Adresse. Kürzen Sie sie, am besten im
Wartungsmodus, mit
php Core/cron/camindo.php audit anonymize-ip --dry-run # nur zählen
php Core/cron/camindo.php audit anonymize-ip
Der Befehl schreibt die Dateien mit neu berechneter Prüfsummenkette und vermerkt das
als Kontrollpunkt „anonymize“. Dateien, deren Kette schon vorher gebrochen ist, lässt er
unverändert und nennt sie; prüfen Sie diese mit audit verify (siehe
Kapitel 14).
Bei einem konkreten Angriff: Werden die vollen Adressen zur weiteren Verfolgung
eines bereits erkannten Angriffs zwingend gebraucht, setzen Sie
privacy.ip_anonymize_days vorübergehend hoch. Das hält nur das Kürzen an – bereits
Gekürztes kommt nicht zurück. Sichern Sie die nötigen Einträge (etwa Export aus dem
Audit-Log, Kopie der Logdatei) an einem geschützten Ort, dokumentieren Sie Anlass und
Dauer und stellen Sie die Frist danach wieder zurück.
Die Frist von 7 Tagen ist eine Vorgabe, keine Rechtsberatung. Stimmen Sie sie mit Ihrem Datenschutzbeauftragten ab.
Löschen nach Lösch-Policy
Was „löschen“ bei einem Datensatz heißt, bestimmt die Lösch-Policy seines Inhaltsbereichs. Jeder Inhaltsbereich (Datensätze, Profile, Beiträge) hat genau eine; Sie wählen sie in den Einstellungen des Bereichs (siehe Kapitel 4) und verwalten sie im Modul „Datenschutz“ → „Lösch-Policies“.
Die Liste zeigt je Policy Instanz, Label und die Kategorien ihrer Blöcke. Ein Klick öffnet die Infoseite (mit den Bereichen, die die Policy benutzen, und dem Reiter „Änderungen“), ein Rechtsklick das Kontextmenü mit „Info“, „Bearbeiten“ und „Löschen“.
Aufbau einer Policy
Eine Policy hat ein Label (Text oder Snippet), gilt für eine Instanz (oder für alle) und besteht aus beliebig vielen Blöcken. Jeder Block sagt: bei welchem Auslöser, zu welchem Zeitpunkt, welche Aktion. Dazu wählen Sie eine Kategorie, die die Aktion einordnet – sie dient nur der Übersicht und hat keine Wirkung:
| Kategorie | Typisch für |
|---|---|
| In den Papierkorb legen | In den Papierkorb |
| Nicht mehr zwingend benötigte Daten entfernen | Einzelne Felder überschreiben (Datenminimierung) |
| Eintrag vollständig anonymisieren | Felder im Audit-Log überschreiben |
| Endgültig löschen | Endgültig löschen, samt Audit-Log; Audit-Log endgültig löschen |
| Auslöser | Wann |
|---|---|
| Datensatz wird gelöscht | gleich auf welchem Weg: Löschdialog der Eingabemaske, Stapelverarbeitung, API record.delete oder CMS:USER_DELETE im Frontend – auch für die Unterdatensätze, die mit ihm gelöscht werden |
| UGC: Profil wurde gelöscht | ein Profil wurde gelöscht – einzeln, per Stapelverarbeitung oder mit seinem ganzen Profil-Bereich. Das löst immer und sofort die Policies aller Datensätze dieses Users aus (Beiträge, Bewertungen …), jeweils die Policy ihres eigenen Bereichs. Hat sie keinen Block mit diesem Auslöser, bleiben die Datensätze stehen. Das lässt sich nicht rückgängig machen: Wird das Profil (oder sein Bereich) wiederhergestellt, holen Sie Beiträge im Papierkorb von Hand zurück |
| Bereich wurde gelöscht | der ganze Inhaltsbereich wurde gelöscht; seine Datensätze liegen dann schon mit ihm im Papierkorb. Darum nicht für „In den Papierkorb“ und „Einzelne Felder überschreiben“. Auch sofort fällige Blöcke laufen erst mit der Zeitplaner-Aufgabe „Lösch-Policies anwenden“; wird der Bereich wiederhergestellt, verfallen sie |
Zeitpunkt: sofort (0) oder nach einer Anzahl Tage bzw. Jahre, gerechnet ab dem Auslösen.
Die Aktionen sind in der Auswahl nach dem gruppiert, was sie betreffen:
| Gruppe | Aktion | Was geschieht |
|---|---|---|
| Datensatz | In den Papierkorb (wird nach … Tagen endgültig gelöscht) | der Datensatz kommt in den Papierkorb dessen, der löscht, und wird nach admin.trash_retention_days (Vorgabe 7 Tage) endgültig gelöscht – samt Mediendateien. Die Auswahl nennt die eingestellte Frist |
| Datensatz | Einzelne Felder überschreiben | Felder des Datensatzes bekommen neue Werte, z. B. status_id = "4", username = "user_{record_id}", kontonummer = "". Mehrsprachige Felder bekommen den Wert in allen Sprachen. Ein Upload-Feld nimmt nur "" an: Die Datei wird samt ihrer Thumbnails sofort vom Server entfernt (auch bei mehrsprachigen Uploads in allen Sprachen) |
| Datensatz | Trigger-Template auf den Datensatz anwenden | die Methode onPrivacyAction() im Trigger-Template des Bereichs bekommt den Datensatz – mehrsprachige Felder je Sprache – und kann ihn ändern; Änderungen werden gespeichert. Für alles, was „Einzelne Felder überschreiben“ nicht ausdrücken kann: verknüpfte Datensätze, mehrsprachige Texte je Sprache, eigene Regeln |
| Audit-Log | Felder im Audit-Log überschreiben | im Verlauf des Datensatzes (Datenbank und var/audit/) werden Vorher und Nachher der genannten Felder ersetzt, bei mehrsprachigen Feldern in allen Sprachen. Zusätzlich: _label (Bezeichnung des Eintrags) und * (alle übrigen Felder – gleich, an welcher Stelle die Zeile steht). Auch spätere Einträge – etwa der letzte Stand beim endgültigen Löschen – werden so geschrieben |
| Audit-Log | Trigger-Template auf das Audit-Log anwenden | onPrivacyAction() im Trigger-Template bekommt jeden Eintrag im Verlauf des Datensatzes und kann Bezeichnung und die Werte vorher/nachher ändern; Geändertes wird zurückgeschrieben |
| Audit-Log | Audit-Log endgültig löschen | der ganze Verlauf des Datensatzes |
| Alles | Endgültig löschen, samt Audit-Log | ohne Papierkorb: der Datensatz, seine Unterdatensätze, ihre Dateien und ihr ganzer Verlauf im Audit-Log sind weg |
Die Felder schreiben Sie je Zeile als feld = "wert". Platzhalter im Wert:
{record_id} (der Datensatz) und {user_id} (der User, dem der Datensatz gehört).
Ein Feld, das es im Bereich nicht gibt, wird übergangen; ein Thumbnail-Feld allein
lässt sich nicht leeren – es verschwindet mit seinem Upload-Feld.
Die beiden Trigger-Aktionen brauchen eine Kennung (Kleinbuchstaben, Ziffern,
_), die das Template als $action erhält – so unterscheidet es mehrere Blöcke. Hat
der Bereich mehrere Trigger-Templates, läuft onPrivacyAction() in jedem, das die
Methode enthält, nacheinander. Hat keines die Methode, scheitert der Auftrag und
steht im Log privacy.
Ob Blöcke einander widersprechen oder doppelt sind, prüft camindo CMS nicht – das ist
Sache dessen, der die Policy einrichtet.
Unabhängig von jeder Policy gilt: Beim Löschen eines Profils – auch mit seinem Bereich – werden seine bekannten Geräte und Sitzungen sofort gelöscht, und Mediendateien verschwinden immer mit dem endgültigen Löschen eines Datensatzes.
Unterdatensätze
Mit einem Datensatz werden auch seine Unterdatensätze gelöscht: die Kinder im Baum, wenn Sie „mit Unterdatensätzen“ löschen (sonst rücken sie eine Ebene nach oben und bleiben unberührt), und immer die fest verknüpften Inhalte (Felder vom Typ „link“), auch die der Kinder. Jeder Unterdatensatz bekommt die Blöcke der Policy seines Bereichs für „Datensatz wird gelöscht“ – mit einer Ausnahme: „In den Papierkorb“ und „Endgültig löschen, samt Audit-Log“ erledigt der Hauptdatensatz für alle mit. Ein Unterdatensatz, für den schon eine Löschung läuft, behält seine Aufträge.
Anonym durch das Profil
Das Audit-Log speichert, wer gehandelt hat, nur als Nummer – bei einem Website-User die Nummer seines Profil-Datensatzes, nie seinen Namen. Diese Verknüpfung bleibt immer bestehen; keine Aktion entfernt sie. Anonym werden die Einträge über das Profil: Ist es endgültig gelöscht oder sind seine personenbezogenen Felder mit „Einzelne Felder überschreiben“ geleert, führt die Nummer zu niemandem mehr – auch nicht in den Einträgen zu Beiträgen und anderen Datensätzen, an denen der User gearbeitet hat. Was in den Einträgen selbst personenbezogen ist (Werte vorher/nachher, Bezeichnung), überschreiben Sie mit „Felder im Audit-Log überschreiben“.
Die Standard-Policies
Bei der Installation entstehen drei Policies für alle Instanzen. Sie lassen sich ändern – als Policies für alle Instanzen nur von einem Superuser –, aber nicht löschen; ein neuer Bereich bekommt die seines Typs vorbelegt.
| Policy | Bereichstyp | Blöcke |
|---|---|---|
| Datensatz löschen | Datensätze | sofort in den Papierkorb (nach 7 Tagen endgültig), auch wenn das Profil des Users gelöscht wird; Audit-Log nach 1 Jahr endgültig löschen |
| Profil löschen | Profile | sofort in den Papierkorb (nach 7 Tagen endgültig); im Audit-Log nach 7 Tagen _label durch user_{record_id} ersetzen und alle übrigen Felder leeren; Audit-Log nach 1 Jahr endgültig löschen |
| User-Generated-Content löschen | Beiträge | sofort in den Papierkorb (nach 7 Tagen endgültig), auch wenn das Profil des Users gelöscht wird; Audit-Log nach 1 Jahr endgültig löschen |
Die Blöcke im Audit-Log gelten in allen drei Standard-Policies auch für den Auslöser „Bereich wurde gelöscht“.
Mit den Standard-Policies ist ein gelöschter Website-User nach 7 Tagen anonym: Das Profil ist endgültig gelöscht, sein eigener Verlauf geleert, und die Einträge zu seinen Beiträgen nennen nur noch eine Nummer, die zu keinem Profil mehr gehört.
Beispiel: Profil anonymisieren statt löschen
Soll ein Profil (etwa wegen Bestellungen) stehen bleiben, aber keine Person mehr erkennen lassen:
| Auslöser | Zeitpunkt | Aktion | Felder |
|---|---|---|---|
| Datensatz wird gelöscht | sofort | Einzelne Felder überschreiben | status_id = "4" |
| Datensatz wird gelöscht | 7 Tage | Einzelne Felder überschreiben | username = "user_{record_id}", name = "", kontonummer = "" |
| Datensatz wird gelöscht | 7 Tage | Felder im Audit-Log überschreiben | _label = "user_{record_id}", * = "" |
| Datensatz wird gelöscht | 1 Jahr | Endgültig löschen, samt Audit-Log |
Die Beiträge des Users werden dabei nach der Policy ihres Bereichs mit dem Auslöser „UGC: Profil wurde gelöscht“ gelöscht. Sollen sie bleiben, nehmen Sie diesen Auslöser aus deren Policy heraus.
Hinweis im Löschdialog
Wer im Backend einen Datensatz löscht, sieht vorher, was die Policy mit der Datenbank tut – sofort und nach Fristen, bei einem Profil auch mit seinen Beiträgen:
Der Profil-Datensatz bleibt bestehen, wird aber wie folgt angepasst: – Status: "Gelöscht"
Nach 7 Tagen: – Benutzername: "user_{record_id}" · – Kontonummer: ""
Nach 1 Jahr: – Endgültige Löschung
72 Beiträge des Users („Kommentare“) bleiben bestehen.
Die Stapelverarbeitung zeigt dasselbe je Bereich. Nach dem Löschen heißt die Meldung je nach Policy „gelöscht“, „angepasst“ oder „geplant“.
Wie es ausgeführt wird
Jeder Block wird beim Löschen ein Auftrag (Tabelle privacy_job) mit
Fälligkeit; die sofort fälligen laufen gleich, die übrigen mit der Zeitplaner-Aufgabe
„Lösch-Policies anwenden“ (Vorgabe: jede Nacht; für Fristen in Stunden öfter
terminieren). Ein Auftrag nimmt die Werte seines Blocks mit – eine spätere Änderung der
Policy wirkt nur auf künftige Löschungen. Scheitert ein Auftrag (etwa weil jemand den
Datensatz gerade bearbeitet), gibt es bis zu 20 Versuche; alles steht im Log
privacy. Aktionen einer Policy gelten als System: Pflichtfelder und Gruppenrechte
werden dabei nicht geprüft, und im Audit-Log steht die Änderung als „nach
Lösch-Policy angepasst“ ohne die bisherigen Werte.
Wird ein Datensatz aus dem Papierkorb wiederhergestellt, verfallen seine noch offenen Aufträge. Ein Datensatz, für den schon eine Löschung läuft, lässt sich nicht erneut löschen.
Beim Löschen eines ganzen Bereichs gehen seine Datensätze mit dem Bereich in
den Papierkorb, und camindo CMS plant für jeden die Blöcke der Bereichs-Policy mit dem
Auslöser „Bereich wurde gelöscht“ – mit den Standard-Policies: Audit-Log nach 1 Jahr
endgültig löschen. Hat die Policy keinen Block für diesen Auslöser, bleibt der Verlauf
stehen. Datensätze, für die es nie Aufträge gab (etwa vor Einführung der
Lösch-Policies gelöscht), bekommen beim endgültigen Löschen wenigstens die
Audit-Log-Blöcke dieses Auslösers, die Fristen gerechnet ab dem Löschen. Der Verlauf
endgültig gelöschter Datensätze im Audit-Log richtet sich nur nach den Policies, nicht
nach audit.retention_days_deleted (das gilt weiter für Bereiche, Felder, Templates
und andere Objekte).
Papierkorb eines Website-Users sofort leeren
Verlangt ein Website-User die sofortige Löschung, leert ein Benutzer mit dem Recht „Papierkorb eines Website-Users endgültig leeren“ (Modul „Werkzeuge“) dessen Papierkorb: „Werkzeuge“ → „Papierkorb eines Users“. Das Profil wählen Sie über die Suche in allen Profil-Bereichen, die Sie lesen dürfen, oder – wenn es selbst schon gelöscht ist – aus der Liste der Profile im Papierkorb. Profil und Beiträge, die im Papierkorb liegen, werden sofort endgültig gelöscht. Benutzer leeren ihren eigenen Papierkorb wie bisher selbst.
Audit-Log von Hand löschen
Das endgültige Löschen – auch über „Werkzeuge“ → „Datenbank“ → „Papierkorb leeren“ – lässt den Verlauf eines Datensatzes im Audit-Log stehen; ihn räumen die Audit-Log-Blöcke der Lösch-Policy nach ihrer Frist ab. Muss ein Verlauf früher verschwinden – etwa weil ein Website-User darauf besteht oder Testdaten im Spiel waren –, löschen Sie ihn auf der Kommandozeile:
php Core/cron/camindo.php audit delete record 4237 --dry-run # nur zählen
php Core/cron/camindo.php audit delete record 4237
php Core/cron/camindo.php audit delete --area=141 --force # alle Datensätze eines Bereichs
Gelöscht wird der ganze Verlauf: die Einträge der Datenbank, die Dateien unter
var/audit/ und die Aufträge der Lösch-Policy zu diesem Datensatz. Noch offene Aufträge
bleiben, solange es den Datensatz gibt – sonst würde er nie zu Ende gelöscht; sie räumen
später auch den Eintrag des endgültigen Löschens ab. --area erfasst jeden Datensatz,
der in dem Bereich liegt oder laut Audit-Log dort lag, auch gelöschte; ohne --force
nennt der Befehl nur die Anzahl. Statt record geht jeder andere Objekttyp des
Audit-Logs, etwa admin 34 für den Verlauf eines Benutzerkontos. Der Vorgang steht mit
Anzahl, aber ohne Inhalte im Log security. Einträge, in denen ein Website-User an
anderen Datensätzen gehandelt hat, erfasst audit delete nicht; sie nennen ihn nur mit
der Nummer seines Profils (siehe Anonym durch das Profil).
Datenexport personenbezogener Daten
Für ein Auskunftsersuchen (Art. 15 DSGVO) oder die Datenübertragbarkeit (Art. 20)
erstellt camindo CMS einen Datenexport: ein Zip mit report.html zum Lesen
und data.json maschinenlesbar, bei Website-Usern zusätzlich files/ mit ihren
hochgeladenen Dateien. Er ist ein Teilauszug – vor der Weitergabe an
die betroffene Person ergänzen Sie ihn um alles, was camindo CMS nicht kennt:
Rechtsgrundlagen, Server-Logfiles außerhalb des CMS, Daten in Buchhaltung, E-Mail und
anderen Systemen.
Den Export erstellt, wer im Modul „Datenschutz“ das Zusatzrecht „Personenbezogene Daten exportieren“ hat (Superuser immer), unter „Datenschutz“ → „Datenexport“:
| Person | Wer darf | Wie |
|---|---|---|
| Website-User | wer den Profil-Bereich lesen darf | Profil suchen (alle lesbaren Profil-Bereiche) oder aus den Profilen im Papierkorb wählen – oder am Profil-Datensatz im Menü ⋮ „Datenexport personenbezogener Daten“ |
| Benutzer | Benutzer der Gruppen, deren Benutzer die Gruppe im Modul „Benutzer“ sehen darf (eigene Gruppe mit „Benutzerdetails lesen: eigene Benutzergruppe“, andere mit „Benutzerdetails lesen: andere Benutzergruppen“) | Benutzer aus der Liste wählen |
Benutzer können ihren Export nicht selbst herunterladen.
Vor dem Erstellen prüft camindo CMS die Vorlagen:
- Gibt es für die Instanz des Profils keine Vorlage für Website-User – weder für seinen Profil-Bereich noch für alle Bereiche – (bei einem Benutzer: weder für seine Gruppe noch für alle Gruppen), erscheint ein Hinweis und keine Schaltfläche zum Erstellen – mit dem Recht dazu legen Sie über „Vorlage anlegen“ gleich eine an.
- Gibt es genau eine, wird sie verwendet.
- Gibt es Vorlagen in mehreren Sprachen, wählen Sie die Sprache der Auskunft. Bei Benutzern wählen Sie unter den Benutzer-Vorlagen aller Ihrer Instanzen.
Darunter steht ein Warnhinweis: Eine Datenauskunft darf nur eine dazu berechtigte Person erstellen – in der Regel der Datenschutzbeauftragte. Und: Jede Datenauskunft wird protokolliert; die betroffene Person erfährt in jeder späteren Auskunft, dass heute eine zu ihr erstellt wurde. Erst wenn Sie „Ich bin berechtigt, die Datenauskunft zu erstellen.“ ankreuzen, entsteht der Export mit „Datenauskunft erstellen“.
Der Export eines Website-Users enthält:
- alle Felder des Profils in allen Sprachen (verschlüsselte im Klartext, Passwörter nur als Hinweis),
- seine Beiträge, nach Bereich gegliedert, samt Dateien – Beiträge im Papierkorb sind als „gelöscht, noch im Papierkorb“ gekennzeichnet,
- endgültig gelöschte Beiträge, die das Audit-Log noch mit ihm verbindet, und was die Lösch-Policy damit noch vorhat (etwa „Audit-Log endgültig löschen: 2027-10-08“),
- Datensätze mit Bezug zum Profil (etwa Bestellungen),
- bekannte Geräte und Sitzungen – direkt darunter ihre Speicherdauer –, zweiter Faktor, offene Links, Warenkorb,
- fehlgeschlagene Anmeldeversuche unter seinem Anmeldenamen bzw. seinem Konto, je mit dem Tag der Löschung,
- Einträge in Protokolldateien, die eindeutig zu ihm gehören: Sperren nach zu
vielen Fehlversuchen (Log
security) und Zeilen des Mail-Logs mit seiner E-Mail-Adresse, etwa nicht zustellbare E-Mails, - das Änderungsprotokoll, die Speicherfristen und die Lösch-Policy seines Profil-Bereichs.
Namen anderer Personen stehen nicht in der Auskunft eines Website-Users: Wer
gehandelt hat, erscheint nur als „Sie selbst“, „Admin“, „User“
(ein anderer Website-User) oder „System“. Gekürzte IP-Adressen erscheinen mit
xxx statt der genullten Stellen (203.0.113.xxx), damit niemand die Null für einen
Teil der Adresse hält.
Der Export eines Benutzers enthält: Kontodaten (Kontaktdaten im Klartext, vom
Passwort nur der Tag der letzten Änderung), Geräte und Sitzungen mit ihrer
Speicherdauer, zweiter Faktor (ohne Schlüssel oder Codes), Anmeldungen (nur mit
audit.log_logins) und fehlgeschlagene Anmeldungen unter seinem Namen – je mit dem Tag
ihrer Löschung –, seine Aktivitäten im Audit-Log mit IP-Adresse (die neuesten 1000),
Änderungen an seinem Konto, interne Nachrichten an ihn (Datum, Absender, Betreff), die
Zahl der Datensätze mit seinem Namen und die Speicherfristen.
Jeder Export steht im Audit-Log. Das Zip liegt bis zum Herunterladen in
var/export/, wird dabei gelöscht und spätestens nach admin.export_retention_hours
(24 Stunden) entfernt.
Die Vorlagen der Datenauskunft
Wie report.html aussieht und welcher Text um die Daten herum steht, bestimmt eine
HTML-Vorlage, die Sie selbst gestalten – etwa mit Ihrem Logo und dem Abschnitt
„Speicherdauer“ Ihrer Datenschutzerklärung. Für Website-User und für
Benutzer gibt es getrennte Vorlagen, jeweils je Instanz und je Sprache –
und auf Wunsch je Profil-Bereich bzw. je Benutzergruppe, etwa:
| Art | Instanz | Gilt für | Sprache |
|---|---|---|---|
| Website-User | Shop | Kunden | Englisch |
| Website-User | Shop | Community | Englisch |
| Website-User | Shop | Alle Profil-Bereiche | Deutsch |
| Benutzer | Shop | Redaktion | Englisch |
| Benutzer | Shop | Alle Benutzergruppen | Englisch |
Beim Export gewinnt die spezifische Vorlage: Für ein Profil im Bereich „Kunden“ gelten die Vorlagen für „Kunden“; gibt es keine, die für alle Profil-Bereiche. Bei einem Benutzer gelten die Vorlagen für seine Benutzergruppe (aus allen Ihren Instanzen), sonst die für alle Gruppen. Sie pflegen die Vorlagen unter „Datenschutz“ → „Vorlagen“; anlegen, ändern und löschen darf, wer das Zusatzrecht „Vorlagen der Datenauskunft pflegen“ hat und die Instanz bearbeiten darf (Superuser immer).
Mit „Neu“ wählen Sie zuerst Art (Website-User oder Benutzer) und Instanz,
im Formular dann „Gilt für“ (Profil-Bereich bzw. Benutzergruppe oder alle) und
die Sprache. Eine zweite Vorlage für dieselbe Kombination lässt sich nicht
speichern. Die neue Vorlage ist mit
der Vorlage des Core für diese Art vorbelegt (Core/install/privacy/privacy_user_en_US.html
bzw. privacy_admin_en_US.html, englisch; sie listen am Anfang alle Platzhalter).
Ohne Vorlage für Website-User gibt es für die Website-User einer Instanz keinen
Datenexport; für einen Benutzer wählen Sie unter den Benutzer-Vorlagen aller Ihrer
Instanzen.
Die Auskunft entsteht in der Sprache der Vorlage: Snippets und die Beschriftungen der Tabellen erscheinen in dieser Sprache, unabhängig von Ihrer Backend-Sprache. Weil Arten, Profil-Bereiche und Benutzergruppen getrennte Vorlagen haben, braucht es keine Unterscheidung innerhalb einer Vorlage.
| Schreibweise | Wirkung |
|---|---|
{name} |
Platzhalter – Werte werden maskiert eingesetzt, Blöcke als fertige HTML-Tabellen |
<!-- if:name -->…<!-- endif:name --> |
Abschnitt nur, wenn die Bedingung gilt; verschachtelbar |
{#modul.schluessel} |
Sprach-Snippet in der Sprache der Vorlage, etwa {#record.gdpr_col_device} |
<!-- Kommentar --> |
Erläuterung für Sie – fällt im Export weg |
Bedingungen: audit, audit_changes_limited / audit_changes_unlimited,
audit_deleted_limited, logcleanup / no_logcleanup (Aufgabe „Logs aufräumen“
eingerichtet), dbbackup / no_dbbackup (Aufgabe „Datenbank-Backup“),
messages_limited, ip_v4_one / ip_v4_two.
Fristen (in beiden Arten): {trash_days}, {device_days}, {ip_days},
{ip_v4_octets}, {message_days}, {audit_changes_days}, {audit_deleted_days},
{session_hours} (Website-User), {session_minutes} (Benutzer), {basket_days},
{unconfirmed_days}, {export_hours}, {backups} (aufbewahrte Datenbanksicherungen),
{log_days}, {security_days}, {attempt_days} (aus der Aufgabe „Logs aufräumen“).
So stimmt der Text immer mit den Einstellungen überein – ändern Sie eine Frist, ändert
sich der Export mit.
Kopf: {subject}, {subject_id}, {domain} (Haupt-Domain der Instanz),
{instance} (Bezeichnung der Instanz), {created}, {lang}, {dir}.
Daten in der Vorlage für Website-User: {profile}, {contents} (nach Bereich gegliedert),
{purged} (endgültig gelöschte Beiträge, die das Audit-Log noch kennt),
{references}, {devices}, {sessions} (je mit der Speicherdauer darunter),
{second_factor}, {tokens}, {basket}, {login_attempts}, {log_lines},
{files}, {audit}, {retention} (die Fristen als fertige Liste), {delete_policy}
(die Lösch-Policy des Profil-Bereichs, je Block ein Satz).
Daten in der Vorlage für Benutzer: {account}, {devices}, {sessions},
{second_factor}, {logins}, {activity}, {activity_note}, {account_changes},
{messages}, {content_counts}, {retention}.
Lassen Sie einen Datenblock weg, fehlt er im Export – die Auskunft muss nach Art. 15
DSGVO aber vollständig sein. Prüfen Sie eine geänderte Vorlage deshalb mit einem
Export zu einem Testkonto. data.json enthält unabhängig von der Vorlage immer alle
Daten.
Was außerhalb von camindo CMS liegt
camindo CMS kann nur kürzen und löschen, was es selbst speichert. Auf Ihrer Liste stehen außerdem:
- Zugriffsprotokolle des Webservers (Apache, nginx, IIS) – sie enthalten die volle IP-Adresse jedes Aufrufs. Kürzen Sie sie im Webserver (etwa mit einem anonymisierenden Log-Format) oder löschen Sie sie nach wenigen Tagen per Logrotation.
- PHP- und Datenbank-Protokolle außerhalb von
var/log/. - Datenbanksicherungen und Server-Backups – sie enthalten den Stand zum Zeitpunkt der Sicherung, also auch noch ungekürzte Adressen der letzten Tage.
- E-Mails, die camindo CMS verschickt hat (Hinweise auf neue Geräte, Formulare).
- Proxies, Load-Balancer, CDN und Firewalls vor der Website.
Checkliste
- [ ] Fristen (
privacy.ip_anonymize_days,security.device_days,audit.*,admin.trash_retention_days, Aufgabe „Logs aufräumen“) mit dem Datenschutzbeauftragten abgestimmt und im Verzeichnis der Verarbeitungstätigkeiten eingetragen - [ ] Zeitplaner-Aufgabe „Logs aufräumen“ eingerichtet
- [ ] nach dem Update einmal
audit anonymize-ipausgeführt - [ ] Lösch-Policies geprüft: jeder Inhaltsbereich hat die passende; aufzubewahrende Daten (Bestellungen, Rechnungen) nicht im Papierkorb, sondern anonymisiert
- [ ] Vorlagen der Datenauskunft angelegt („Datenschutz“ → „Vorlagen“): für Website-User je Instanz mit Community (bei Bedarf je Profil-Bereich), für Benutzer mindestens eine (bei Bedarf je Benutzergruppe) – angepasst und mit einem Testkonto geprüft
- [ ] Rechte vergeben: „Personenbezogene Daten exportieren“, „Lösch-Policies anlegen und ändern“, „Vorlagen der Datenauskunft pflegen“ (Modul „Datenschutz“), „Papierkorb eines Website-Users endgültig leeren“ (Modul „Werkzeuge“)
- [ ] entschieden, ob erfolgreiche Anmeldungen aufgezeichnet werden (
audit.log_logins, Vorgabe nein) und wie lange Anmeldungen bleiben (audit.retention_days_logins) - [ ] Frist der internen Nachrichten (
privacy.message_days) festgelegt; bei ausgeschiedenen Mitarbeitern Nachrichten gelöscht - [ ] Zugriffsprotokolle des Webservers gekürzt oder kurzfristig gelöscht
- [ ] hinter einem Proxy:
security.trusted_proxiesgesetzt – sonst steht überall die Adresse des Proxys - [ ] Mitarbeiter über das Audit-Log informiert, Betriebsvereinbarung bzw. Richtlinie
- [ ] Datenschutzerklärung der Website nennt Cookies, Formulare, Community-Funktionen
- [ ] Ablauf für Auskunfts- und Löschersuchen festgelegt (wer, Frist ein Monat)
Stand: camindo CMS 9.1.3