Im letzten Beitrag (DSGVO: Eingabekontrolle) wurde das Thema Löschen von personenbezogenen Daten kurz angerissen. Dieses Mal möchte ich dies etwas weiter vertiefen.
Die DSGVO zwingt zur Datensparsamkeit. Personenbezogene Daten dürfen nur gespeichert werden, wenn ein so genannter Rechtsgrund vorliegt. Dieser Rechtsgrund kann z.B. eine Geschäftsverbindung sein. Auch Geschäftsanbahnung oder eigene Interessen sind hier von Belang, müssen aber plausibel sein. Auf keinen Fall sind dies Gründe, die Daten unbegrenzt aufzubewahren. Ein Grund, welcher alle vorigen in den Schatten stellt, ist die Einwilligung: liegt diese vor, sind alle anderen Rechtsgründe hinfällig. Die Datensätze sollten regelmäßig (mindestens einmal jährlich) überprüft werden, ob der Rechtsgrund noch vorliegt.
Fangen wir aber mal mit der einfachsten Variante an: Der Kontakt (in unserem Beispiel KUNDE) gibt seine Einwilligung. Dies sollten wir mit Datum (und am besten auch noch mit objektivem Nachweis, z.B. einem eingescannten Einwilligungsformular) speichern.
ALTER TABLE KUNDE ADD COLUMN eingewilligt TIMESTAMP; CREATE TABLE Formulare (ID AutoInc, Kunde_ID LongInt, Zeit TimeStamp, Benutzer CIChar(30), Titel CICHAR(50), Formular BLOB); CREATE PROCEDURE Einwilligung_erteilen(KNR CHAR(10), Datum TIMESTAMP, Nachweis BLOB) BEGIN DECLARE @id LONGINT; @id=(SELECT TOP 1 id FROM KUNDE WHERE KNR LIKE _KNR); IF COALESCE(@id,-1)>0 THEN UPDATE kunde SET eingewilligt=_Datum WHERE ID = @id; INSERT INTO log_kunde (Kunde_ID, Zeit, Benutzer, OP, Bemerkung) VALUES(@id, now(), user(), 'u', 'Kundeneinwilligung erteilt'); INSERT INTO Formulare(Kunde_ID, Zeit, Benutzer, Titel, Formular) SELECT @id, now(), user(), 'Kundeneinwilligung', Nachweis FROM __input WHERE Nachweis IS NOT NULL; END; END;
Besondere Berechtigungen müssen nicht gesetzt werden, da der ADS innerhalb von Triggern und Stored Procedures die Berechtigungen nicht gesondert abfrägt.
Wird die Einwilligung wieder entzogen, so sollte dies auch entsprechend vermerkt werden.
CREATE PROCEDURE Einwilligung_entziehen(KNR CHAR(10), Datum TIMESTAMP, Nachweis BLOB) BEGIN DECLARE @id LONGINT; @id=(SELECT TOP 1 id FROM KUNDE WHERE KNR LIKE _KNR); IF COALESCE(@id,-1)>0 THEN UPDATE kunde SET eingewilligt=NULL WHERE ID = @id; INSERT INTO log_kunde (Kunde_ID, Zeit, Benutzer, OP, Bemerkung) VALUES(@id, now(), user(), 'u', 'Kundeneinwilligung entzogen'); INSERT INTO Formulare(Kunde_ID, Zeit, Benutzer, Titel, Formular) SELECT @id, now(), user(), 'Entzug Kundeneinwilligung', Nachweis FROM __input WHERE Nachweis IS NOT NULL; END; END;
Regelmäßig sollte eine Datenbereinigung durchgeführt werden. Es gibt durchaus gute Gründe, die Datensätze nicht zu löschen, sondern nur inaktiv zu schalten. Inaktive Datensätze können – wenn kein Rechtsgrund dagegen spricht, anonymisiert werden, so dass weiterhin historische Betrachtungen möglich sind.
Im ersten Schritt ersetzen wir das Löschen durch ein deaktivieren.
ALTER TABLE KUNDE ADD COLUMN deaktiviert TIMESTAMP; DROP TRIGGER kunde.trig_del; CREATE TRIGGER trig_del ON Kunde INSTEAD OF DELETE BEGIN DECLARE @Bem String; @Bem='KNR = '+COALESCE(TRIM(__old.Knr),'')+'; '; INSERT INTO Log_Kunde(Kunde_ID, Zeit, Benutzer, OP, Bemerkung) VALUES (__old.id, now(), user(), 'd', @Bem); UPDATE kunde SET deaktiviert=now() WHERE ROWID=::stmt.TrigRowID; END;
Für die Applikation sollte entsprechend ein Filter gesetzt werden. Dies kann auch durch Umstellen der Tabellenabfrage auf eine View erfolgen:
CREATE VIEW Aktiver_Kunde AS SELECT * FROM Kunde WHERE deaktiviert IS NULL;
Vor dem eigentlichen Anonymisieren sollte überprüft werden, ob kein Rechtsgrund vorliegt. Wie oben bereits genannt ist der einfachste Rechtsgrund die Einwilligung. Alle weiteren Rechtsgründe werden Schritt für Schritt hinzugefügt:
CREATE PACKAGE DSGVO; CREATE FUNCTION DSGVO.Rechtsgrund (Kunde_id LongInt) RETURNS LOGICAL BEGIN DECLARE @Rechtsgrund LOGICAL; @Rechtsgrund = FALSE; --prüfen der Einwilligung @Rechtsgrund = exists(SELECT * FROM kunde WHERE id=Kunde_id AND eingewilligt IS NOT NULL); RETURN @Rechtsgrund; END;
Das Anonymisieren übernimmt eine Stored Procedure:
CREATE PROCEDURE anonymisieren (Kunde_id LongInt, success LOGICAL OUTPUT) BEGIN UPDATE kunde SET Name = SUBSTRING(Name,1,1), Vorname = SUBSTRING(Vorname,1,1), Telefon = NULL, Email = NULL, Geburtstag = CAST(CREATETIMESTAMP(year(Geburtstag), 1,1,0,0,0,0) AS SQL_DATE), Religion = NULL WHERE id=_kunde_id AND NOT dsgvo.Rechtsgrund(_Kunde_id); INSERT INTO __OUTPUT(success) VALUES (::stmt.UpdateCount > 0); INSERT INTO Log_Kunde(Kunde_ID, Zeit, Benutzer, OP, Bemerkung) VALUES (_kunde_id, now(), user(), 'u', 'Datensatz anonymisiert'); END;
Eine Bereinigung der gesamten Datenbank kann über ein Skript (hier auch in eine Stored Procedure verpackt) erfolgen. Es wird ein Cursor geöffnet, welcher alle Datensätze ohne Rechtsgrund beinhaltet. In der Iteration darüber wird für jeden Datensatz die zuvor erstellte Stored Procedure aufgerufen. Es geht sicherlich auch performanter, in dieser Lösung sind jedoch die anonymisierten Datensätze als Ausgabemenge enthalten.
CREATE PROCEDURE alle_anonymisieren (Kunde_id LongInt OUTPUT, success LOGICAL OUTPUT) BEGIN DECLARE @kunde CURSOR AS SELECT * FROM kunde WHERE NOT dsgvo.Rechtsgrund(id); OPEN @kunde; WHILE FETCH @kunde DO INSERT INTO __OUTPUT(kunde_id, success) SELECT @kunde.id, a.success FROM(EXECUTE PROCEDURE anonymisieren(@kunde.id)) a; END; CLOSE @kunde; END;
REFERENZ: DSGVO-Reihe
Sollte man nicht in „anonymisieren“ den Eintrag ins Log von „success“ abhängig machen? Ein Kunde mit positivem Rechtsgrund wird ja eben nicht anonymisiert. Ich sehe natürlich, dass in „alle_anonymisieren“ diese Kunden von vorneherein gar nicht mit selektiert werden, aber wenn man mal „anonymisieren“ separat aufruft…
Es ist die Eigenschaft einer ID, eindeutig zu sein. Es wird also maximal ein Datensatz anonymisiert. Oder keiner, wenn noch ein Rechtsgrund vorliegt. Daher reicht die Abfrage auf UpdateCount.