DSGVO-konforme Entwicklung in ABAP Cloud

kategorie
Security
Veröffentlicht
autor
Johannes

Die Datenschutz-Grundverordnung (DSGVO) stellt konkrete Anforderungen an die Softwareentwicklung. Als ABAP Cloud Entwickler musst du diese Anforderungen technisch umsetzen. Dieser Artikel zeigt, wie du DSGVO-konforme Anwendungen in ABAP Cloud entwickelst.

DSGVO-Anforderungen für Entwickler

Die DSGVO definiert Rechte für betroffene Personen, die sich direkt auf die Entwicklung auswirken:

RechtArtikelTechnische Umsetzung
AuskunftsrechtArt. 15Datenexport-Funktion
Recht auf BerichtigungArt. 16Änderungsfunktionen
Recht auf LöschungArt. 17Löschkonzept
Recht auf EinschränkungArt. 18Sperrfunktion
DatenübertragbarkeitArt. 20Export in Standardformat
WiderspruchsrechtArt. 21Consent-Verwaltung

Technische Prinzipien

Die DSGVO fordert Privacy by Design und Privacy by Default:

Privacy by Design:
┌─────────────────────────────────────────────────────┐
│ Datenschutz in alle Entwicklungsphasen integrieren │
├─────────────────────────────────────────────────────┤
│ • Minimale Datenerhebung │
│ • Zweckbindung prüfen │
│ • Aufbewahrungsfristen definieren │
│ • Verschlüsselung implementieren │
│ • Zugriffsprotokollierung aktivieren │
└─────────────────────────────────────────────────────┘

Löschkonzept implementieren (Data Retention)

Ein Löschkonzept definiert, wann und wie personenbezogene Daten gelöscht werden. In ABAP Cloud implementierst du dies über Retention Rules und entsprechende Lösch-Jobs.

Retention-Tabelle definieren

@EndUserText.label: 'Datenkategorie Aufbewahrungsfristen'
@AbapCatalog.enhancement.category: #NOT_EXTENSIBLE
define table zretention_rules {
key client : abap.clnt not null;
key data_category : abap.char(30) not null;
retention_period_days : abap.int4;
deletion_method : abap.char(10); -- HARD, SOFT, ANON
legal_basis : abap.char(100);
last_review_date : abap.dats;
responsible_role : abap.char(30);
}

Löschservice implementieren

CLASS zcl_gdpr_deletion_service DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
TYPES:
BEGIN OF ty_deletion_result,
data_category TYPE string,
records_deleted TYPE i,
records_failed TYPE i,
deletion_date TYPE timestampl,
END OF ty_deletion_result,
tt_deletion_results TYPE STANDARD TABLE OF ty_deletion_result WITH EMPTY KEY.
METHODS:
execute_retention_deletion
RETURNING VALUE(rt_results) TYPE tt_deletion_results,
delete_customer_data
IMPORTING iv_customer_id TYPE zcustomer_id
iv_deletion_type TYPE string DEFAULT 'HARD'
RETURNING VALUE(rv_success) TYPE abap_bool,
get_retention_period
IMPORTING iv_data_category TYPE string
RETURNING VALUE(rv_days) TYPE i.
PRIVATE SECTION.
METHODS:
get_deletion_candidates
IMPORTING iv_data_category TYPE string
iv_cutoff_date TYPE d
RETURNING VALUE(rt_keys) TYPE ztt_entity_keys,
perform_hard_delete
IMPORTING it_keys TYPE ztt_entity_keys
RETURNING VALUE(rv_count) TYPE i,
perform_soft_delete
IMPORTING it_keys TYPE ztt_entity_keys
RETURNING VALUE(rv_count) TYPE i,
log_deletion
IMPORTING iv_category TYPE string
iv_count TYPE i
iv_method TYPE string.
ENDCLASS.
CLASS zcl_gdpr_deletion_service IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_results) = execute_retention_deletion( ).
LOOP AT lt_results INTO DATA(ls_result).
out->write( |Kategorie: { ls_result-data_category }| ).
out->write( |Gelöscht: { ls_result-records_deleted }| ).
out->write( |Fehlgeschlagen: { ls_result-records_failed }| ).
out->write( |---| ).
ENDLOOP.
ENDMETHOD.
METHOD execute_retention_deletion.
" Alle aktiven Retention Rules abrufen
SELECT * FROM zretention_rules
INTO TABLE @DATA(lt_rules).
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
LOOP AT lt_rules INTO DATA(ls_rule).
" Stichtag berechnen
DATA(lv_cutoff_date) = lv_today - ls_rule-retention_period_days.
" Löschkandidaten ermitteln
DATA(lt_candidates) = get_deletion_candidates(
iv_data_category = ls_rule-data_category
iv_cutoff_date = lv_cutoff_date ).
" Löschung durchführen
DATA(lv_deleted) = COND #(
WHEN ls_rule-deletion_method = 'HARD'
THEN perform_hard_delete( lt_candidates )
WHEN ls_rule-deletion_method = 'SOFT'
THEN perform_soft_delete( lt_candidates )
WHEN ls_rule-deletion_method = 'ANON'
THEN anonymize_records( lt_candidates )
ELSE 0 ).
" Ergebnis protokollieren
log_deletion(
iv_category = ls_rule-data_category
iv_count = lv_deleted
iv_method = ls_rule-deletion_method ).
APPEND VALUE #(
data_category = ls_rule-data_category
records_deleted = lv_deleted
records_failed = lines( lt_candidates ) - lv_deleted
deletion_date = utclong_current( )
) TO rt_results.
ENDLOOP.
ENDMETHOD.
METHOD delete_customer_data.
" Einzellöschung für Betroffenenanfrage
TRY.
CASE iv_deletion_type.
WHEN 'HARD'.
" Unwiderrufliche Löschung
DELETE FROM zcustomer WHERE customer_id = @iv_customer_id.
DELETE FROM zcustomer_contact WHERE customer_id = @iv_customer_id.
DELETE FROM zcustomer_address WHERE customer_id = @iv_customer_id.
WHEN 'SOFT'.
" Markierung als gelöscht
UPDATE zcustomer
SET is_deleted = @abap_true,
deletion_date = @( cl_abap_context_info=>get_system_date( ) )
WHERE customer_id = @iv_customer_id.
WHEN 'ANONYMIZE'.
" Anonymisierung statt Löschung
UPDATE zcustomer
SET first_name = 'ANONYMISIERT',
last_name = 'ANONYMISIERT',
email = '[email protected]',
phone = '000000000',
is_anonymized = @abap_true
WHERE customer_id = @iv_customer_id.
ENDCASE.
rv_success = abap_true.
CATCH cx_sy_open_sql_db.
rv_success = abap_false.
ENDTRY.
ENDMETHOD.
METHOD get_retention_period.
SELECT SINGLE retention_period_days
FROM zretention_rules
WHERE data_category = @iv_data_category
INTO @rv_days.
IF sy-subrc <> 0.
rv_days = 365 * 10. " Default: 10 Jahre
ENDIF.
ENDMETHOD.
" ... weitere Methoden
ENDCLASS.

RAP Action für Löschung

" Behavior Definition
define behavior for ZI_Customer alias Customer
implementation in class zbp_i_customer unique
{
// DSGVO-Löschung als Action
action deletePersonalData result [1] $self;
// Soft Delete statt physischer Löschung
delete ( features : instance );
}
" Behavior Implementation
METHOD deletePersonalData.
DATA(lo_deletion_service) = NEW zcl_gdpr_deletion_service( ).
LOOP AT keys INTO DATA(ls_key).
DATA(lv_success) = lo_deletion_service->delete_customer_data(
iv_customer_id = ls_key-CustomerId
iv_deletion_type = 'ANONYMIZE' ).
IF lv_success = abap_true.
" Ergebnis zurückgeben
READ ENTITIES OF zi_customer IN LOCAL MODE
ENTITY Customer
ALL FIELDS WITH VALUE #( ( %key = ls_key-%key ) )
RESULT DATA(lt_customers).
result = VALUE #( FOR customer IN lt_customers
( %tky = customer-%tky
%param = customer ) ).
ELSE.
APPEND VALUE #(
%tky = ls_key-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Löschung fehlgeschlagen' )
) TO reported-customer.
ENDIF.
ENDLOOP.
ENDMETHOD.

Anonymisierung von Daten

Anonymisierung macht Daten unwiderruflich nicht mehr einer Person zuordenbar. Sie ist eine Alternative zur Löschung, wenn die Daten für Statistiken benötigt werden.

Anonymisierungsservice

CLASS zcl_gdpr_anonymization DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_field_rule,
field_name TYPE string,
anon_method TYPE string, -- MASK, HASH, RANDOM, CONSTANT, NULLIFY
constant_value TYPE string,
END OF ty_field_rule,
tt_field_rules TYPE STANDARD TABLE OF ty_field_rule WITH EMPTY KEY.
METHODS:
anonymize_customer
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rv_success) TYPE abap_bool,
anonymize_field
IMPORTING iv_value TYPE any
iv_method TYPE string
iv_constant TYPE string OPTIONAL
RETURNING VALUE(rv_result) TYPE string,
generate_pseudonym
IMPORTING iv_original TYPE string
RETURNING VALUE(rv_pseudonym) TYPE string.
ENDCLASS.
CLASS zcl_gdpr_anonymization IMPLEMENTATION.
METHOD anonymize_customer.
" Anonymisierungsregeln definieren
DATA(lt_rules) = VALUE tt_field_rules(
( field_name = 'FIRST_NAME' anon_method = 'CONSTANT' constant_value = 'ANON' )
( field_name = 'LAST_NAME' anon_method = 'CONSTANT' constant_value = 'ANON' )
( field_name = 'EMAIL' anon_method = 'HASH' )
( field_name = 'PHONE' anon_method = 'MASK' )
( field_name = 'BIRTH_DATE' anon_method = 'NULLIFY' )
( field_name = 'ADDRESS' anon_method = 'CONSTANT' constant_value = 'Anonymisiert' )
).
" Kundendaten laden
SELECT SINGLE *
FROM zcustomer
WHERE customer_id = @iv_customer_id
INTO @DATA(ls_customer).
IF sy-subrc <> 0.
rv_success = abap_false.
RETURN.
ENDIF.
" Felder anonymisieren
ls_customer-first_name = anonymize_field(
iv_value = ls_customer-first_name
iv_method = 'CONSTANT'
iv_constant = 'ANON' ).
ls_customer-last_name = anonymize_field(
iv_value = ls_customer-last_name
iv_method = 'CONSTANT'
iv_constant = 'ANON' ).
ls_customer-email = anonymize_field(
iv_value = ls_customer-email
iv_method = 'HASH' ).
ls_customer-phone = anonymize_field(
iv_value = ls_customer-phone
iv_method = 'MASK' ).
ls_customer-is_anonymized = abap_true.
ls_customer-anonymization_date = cl_abap_context_info=>get_system_date( ).
" Anonymisierte Daten speichern
UPDATE zcustomer FROM @ls_customer.
rv_success = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
METHOD anonymize_field.
CASE iv_method.
WHEN 'MASK'.
" Teilweise maskieren (z.B. ***1234)
DATA(lv_length) = strlen( iv_value ).
IF lv_length > 4.
rv_result = repeat( val = '*' occ = lv_length - 4 ) &&
substring( val = iv_value off = lv_length - 4 len = 4 ).
ELSE.
rv_result = repeat( val = '*' occ = lv_length ).
ENDIF.
WHEN 'HASH'.
" Einweg-Hash (nicht rückrechenbar)
TRY.
cl_abap_message_digest=>create_md5(
EXPORTING
if_data = cl_abap_codepage=>convert_to( iv_value )
IMPORTING
ef_hashstring = rv_result ).
CATCH cx_abap_message_digest.
rv_result = 'HASH_ERROR'.
ENDTRY.
WHEN 'RANDOM'.
" Zufallswert generieren
rv_result = cl_system_uuid=>create_uuid_x16_static( ).
WHEN 'CONSTANT'.
" Fester Ersatzwert
rv_result = iv_constant.
WHEN 'NULLIFY'.
" Leerwert
rv_result = ''.
WHEN OTHERS.
rv_result = iv_value.
ENDCASE.
ENDMETHOD.
METHOD generate_pseudonym.
" Deterministischer Pseudonym (gleiches Original = gleiches Pseudonym)
DATA(lv_salt) = 'GDPR_PSEUDONYM_SALT_2024'.
DATA(lv_input) = lv_salt && iv_original.
TRY.
cl_abap_message_digest=>create_sha256(
EXPORTING
if_data = cl_abap_codepage=>convert_to( lv_input )
IMPORTING
ef_hashstring = DATA(lv_hash) ).
" Kürzeren, lesbaren Pseudonym erstellen
rv_pseudonym = 'PSN_' && substring( val = lv_hash len = 12 ).
CATCH cx_abap_message_digest.
rv_pseudonym = 'PSN_ERROR'.
ENDTRY.
ENDMETHOD.
ENDCLASS.

Pseudonymisierung vs. Anonymisierung

AspektPseudonymisierungAnonymisierung
RückführbarJa, mit SchlüsselNein
DSGVO-StatusPersonenbezogenNicht personenbezogen
AnwendungVerarbeitungStatistik, Archiv
BeispielPSN_A3F82BANON

Auskunftsrecht umsetzen (Data Export)

Betroffene haben das Recht, alle über sie gespeicherten Daten in einem maschinenlesbaren Format zu erhalten.

Datenexport-Service

CLASS zcl_gdpr_data_export DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF ty_export_section,
category TYPE string,
data TYPE REF TO data,
END OF ty_export_section,
tt_export_sections TYPE STANDARD TABLE OF ty_export_section WITH EMPTY KEY.
METHODS:
export_customer_data
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rv_json) TYPE string,
export_as_json
IMPORTING it_sections TYPE tt_export_sections
RETURNING VALUE(rv_json) TYPE string,
create_export_request
IMPORTING iv_customer_id TYPE zcustomer_id
iv_request_type TYPE string
RETURNING VALUE(rv_request_id) TYPE sysuuid_x16.
ENDCLASS.
CLASS zcl_gdpr_data_export IMPLEMENTATION.
METHOD export_customer_data.
DATA: lt_sections TYPE tt_export_sections.
" 1. Stammdaten
SELECT SINGLE customer_id, first_name, last_name, email,
phone, birth_date, created_at
FROM zcustomer
WHERE customer_id = @iv_customer_id
INTO @DATA(ls_customer).
IF sy-subrc = 0.
GET REFERENCE OF ls_customer INTO DATA(lr_customer).
APPEND VALUE #( category = 'Stammdaten' data = lr_customer ) TO lt_sections.
ENDIF.
" 2. Adressen
SELECT address_type, street, city, postal_code, country
FROM zcustomer_address
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_addresses).
IF sy-subrc = 0.
GET REFERENCE OF lt_addresses INTO DATA(lr_addresses).
APPEND VALUE #( category = 'Adressen' data = lr_addresses ) TO lt_sections.
ENDIF.
" 3. Bestellungen
SELECT order_id, order_date, status, total_amount, currency
FROM zorder
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_orders).
IF sy-subrc = 0.
GET REFERENCE OF lt_orders INTO DATA(lr_orders).
APPEND VALUE #( category = 'Bestellungen' data = lr_orders ) TO lt_sections.
ENDIF.
" 4. Kommunikation
SELECT communication_date, channel, subject
FROM zcommunication_log
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_communications).
IF sy-subrc = 0.
GET REFERENCE OF lt_communications INTO DATA(lr_comms).
APPEND VALUE #( category = 'Kommunikation' data = lr_comms ) TO lt_sections.
ENDIF.
" 5. Consent-Historie
SELECT consent_type, consent_date, consent_status, ip_address
FROM zconsent_log
WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_consents).
IF sy-subrc = 0.
GET REFERENCE OF lt_consents INTO DATA(lr_consents).
APPEND VALUE #( category = 'Einwilligungen' data = lr_consents ) TO lt_sections.
ENDIF.
" Als JSON exportieren
rv_json = export_as_json( lt_sections ).
ENDMETHOD.
METHOD export_as_json.
DATA: lo_json TYPE REF TO cl_sxml_string_writer.
" JSON-Struktur aufbauen
DATA(lo_writer) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
lo_writer->open_element( name = 'gdpr_export' ).
lo_writer->open_element( name = 'export_date' ).
lo_writer->write_value( |{ cl_abap_context_info=>get_system_date( ) DATE = ISO }| ).
lo_writer->close_element( ).
LOOP AT it_sections INTO DATA(ls_section).
lo_writer->open_element( name = ls_section-category ).
" Daten zu JSON serialisieren
DATA(lv_section_json) = /ui2/cl_json=>serialize(
data = ls_section-data->*
pretty_name = /ui2/cl_json=>pretty_mode-low_case ).
lo_writer->write_value( lv_section_json ).
lo_writer->close_element( ).
ENDLOOP.
lo_writer->close_element( ).
rv_json = cl_abap_codepage=>convert_from( lo_writer->get_output( ) ).
ENDMETHOD.
METHOD create_export_request.
" Export-Anfrage protokollieren
rv_request_id = cl_system_uuid=>create_uuid_x16_static( ).
INSERT INTO zgdpr_requests VALUES @( VALUE #(
request_id = rv_request_id
customer_id = iv_customer_id
request_type = iv_request_type " EXPORT, DELETE, RECTIFY
request_date = cl_abap_context_info=>get_system_date( )
request_time = cl_abap_context_info=>get_system_time( )
status = 'NEW'
requestor = cl_abap_context_info=>get_user_technical_name( )
) ).
ENDMETHOD.
ENDCLASS.

RAP Action für Datenexport

" Behavior Definition
define behavior for ZC_Customer alias Customer
{
// Datenexport als Action mit JSON-Rückgabe
action ( features : instance ) exportMyData
result [1] $self;
}
" In der Projection View einen virtuellen Feld für Export-Link
define view entity ZC_Customer as projection on ZI_Customer
{
key CustomerId,
FirstName,
LastName,
Email,
// Virtuelles Feld für Export-Download
@UI.hidden: true
cast( '' as abap.string ) as ExportData
}

Einwilligungen müssen dokumentiert, widerrufbar und nachweisbar sein.

@EndUserText.label: 'Consent Kategorien'
define table zconsent_types {
key client : abap.clnt not null;
key consent_type : abap.char(30) not null;
description : abap.char(200);
legal_basis : abap.char(100);
is_mandatory : abap_boolean;
default_duration : abap.int4; -- Tage
}
@EndUserText.label: 'Consent Log'
define table zconsent_log {
key client : abap.clnt not null;
key consent_id : sysuuid_x16 not null;
customer_id : zcustomer_id;
consent_type : abap.char(30);
consent_status : abap.char(10); -- GRANTED, WITHDRAWN, EXPIRED
consent_date : abap.dats;
consent_time : abap.tims;
expiry_date : abap.dats;
ip_address : abap.char(45);
user_agent : abap.char(500);
consent_text : abap.string;
withdrawal_date : abap.dats;
withdrawal_reason : abap.char(200);
}
CLASS zcl_consent_manager DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
CONSTANTS:
BEGIN OF gc_consent_status,
granted TYPE string VALUE 'GRANTED',
withdrawn TYPE string VALUE 'WITHDRAWN',
expired TYPE string VALUE 'EXPIRED',
END OF gc_consent_status.
METHODS:
grant_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
iv_ip_address TYPE string OPTIONAL
iv_user_agent TYPE string OPTIONAL
RETURNING VALUE(rv_consent_id) TYPE sysuuid_x16,
withdraw_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
iv_reason TYPE string OPTIONAL
RETURNING VALUE(rv_success) TYPE abap_bool,
check_consent
IMPORTING iv_customer_id TYPE zcustomer_id
iv_consent_type TYPE string
RETURNING VALUE(rv_granted) TYPE abap_bool,
get_consent_status
IMPORTING iv_customer_id TYPE zcustomer_id
RETURNING VALUE(rt_consents) TYPE ztt_consent_status,
expire_old_consents.
ENDCLASS.
CLASS zcl_consent_manager IMPLEMENTATION.
METHOD grant_consent.
" Consent-Typ-Details laden
SELECT SINGLE * FROM zconsent_types
WHERE consent_type = @iv_consent_type
INTO @DATA(ls_type).
IF sy-subrc <> 0.
RETURN.
ENDIF.
" Ablaufdatum berechnen
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
DATA(lv_expiry) = COND #(
WHEN ls_type-default_duration > 0
THEN lv_today + ls_type-default_duration
ELSE '99991231' ).
" Neuen Consent anlegen
rv_consent_id = cl_system_uuid=>create_uuid_x16_static( ).
INSERT INTO zconsent_log VALUES @( VALUE #(
consent_id = rv_consent_id
customer_id = iv_customer_id
consent_type = iv_consent_type
consent_status = gc_consent_status-granted
consent_date = lv_today
consent_time = cl_abap_context_info=>get_system_time( )
expiry_date = lv_expiry
ip_address = iv_ip_address
user_agent = iv_user_agent
consent_text = ls_type-description
) ).
" Alten Consent invalidieren
UPDATE zconsent_log
SET consent_status = @gc_consent_status-withdrawn
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_id <> @rv_consent_id
AND consent_status = @gc_consent_status-granted.
ENDMETHOD.
METHOD withdraw_consent.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
UPDATE zconsent_log
SET consent_status = @gc_consent_status-withdrawn,
withdrawal_date = @lv_today,
withdrawal_reason = @iv_reason
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_status = @gc_consent_status-granted.
rv_success = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
METHOD check_consent.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
SELECT SINGLE @abap_true
FROM zconsent_log
WHERE customer_id = @iv_customer_id
AND consent_type = @iv_consent_type
AND consent_status = @gc_consent_status-granted
AND expiry_date >= @lv_today
INTO @rv_granted.
ENDMETHOD.
METHOD get_consent_status.
SELECT consent_type, consent_status, consent_date, expiry_date
FROM zconsent_log
WHERE customer_id = @iv_customer_id
ORDER BY consent_date DESCENDING
INTO TABLE @rt_consents.
ENDMETHOD.
METHOD expire_old_consents.
DATA(lv_today) = cl_abap_context_info=>get_system_date( ).
UPDATE zconsent_log
SET consent_status = @gc_consent_status-expired
WHERE expiry_date < @lv_today
AND consent_status = @gc_consent_status-granted.
ENDMETHOD.
ENDCLASS.
METHOD send_marketing_email.
DATA(lo_consent) = NEW zcl_consent_manager( ).
" Prüfen ob Marketing-Einwilligung vorliegt
IF lo_consent->check_consent(
iv_customer_id = iv_customer_id
iv_consent_type = 'MARKETING_EMAIL' ) = abap_false.
" Keine Einwilligung - E-Mail nicht senden
RAISE EXCEPTION TYPE zcx_no_consent
EXPORTING
textid = zcx_no_consent=>no_marketing_consent.
ENDIF.
" Marketing-E-Mail senden
" ...
ENDMETHOD.

SAP Information Lifecycle Management Integration

SAP ILM bietet eine integrierte Lösung für Datenaufbewahrung und Löschung.

ILM-Konfiguration

CLASS zcl_ilm_integration DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
register_business_object
IMPORTING iv_object_name TYPE string
iv_table_name TYPE tabname,
set_retention_rule
IMPORTING iv_object_name TYPE string
iv_retention_days TYPE i,
execute_blocking
IMPORTING iv_object_name TYPE string,
execute_destruction
IMPORTING iv_object_name TYPE string.
ENDCLASS.
CLASS zcl_ilm_integration IMPLEMENTATION.
METHOD register_business_object.
" ILM Objekt registrieren über SARA/ILM_DESTRUCTION
" Hinweis: In ABAP Cloud über APIs
" ...
ENDMETHOD.
METHOD set_retention_rule.
" Aufbewahrungsregel in ILM setzen
" Wird typischerweise über Customizing gemacht
" ...
ENDMETHOD.
METHOD execute_blocking.
" Residence/Retention-Phase
" Daten werden für Änderungen gesperrt
" ...
ENDMETHOD.
METHOD execute_destruction.
" End of Purpose - Daten löschen
" Wird über ILM Job ausgeführt
" ...
ENDMETHOD.
ENDCLASS.

ILM-fähige CDS View

@EndUserText.label: 'Kundenstamm mit ILM'
@ObjectModel.dataCategory: #MASTER
@ObjectModel.lifecycle.dateField: 'CreatedAt' -- Für ILM-Bestimmung
define view entity ZI_Customer_ILM
as select from zcustomer
{
key customer_id as CustomerId,
first_name as FirstName,
last_name as LastName,
@Semantics.systemDateTime.createdAt: true
created_at as CreatedAt,
@ObjectModel.lifecycle.status: #BLOCKED
is_blocked as IsBlocked,
@ObjectModel.lifecycle.status: #DELETED
is_deleted as IsDeleted
}

Checkliste für DSGVO-Compliance

Entwicklungs-Checkliste

PrüfpunktUmgesetztBemerkung
Datenminimierung
Nur notwendige Felder erfassen
Pflichtfelder auf Minimum reduziert
Keine Erfassung “auf Vorrat”
Zweckbindung
Verwendungszweck dokumentiert
Consent für jeden Zweck eingeholt
Keine Zweckentfremdung
Speicherbegrenzung
Aufbewahrungsfristen definiert
Löschjobs implementiert
Archivierung konfiguriert
Betroffenenrechte
Auskunftsfunktion vorhanden
Berichtigungsfunktion vorhanden
Löschfunktion vorhanden
Sperrfunktion vorhanden
Export-Funktion vorhanden
Sicherheit
Zugriffskontrolle implementiert
Verschlüsselung bei Übertragung
Protokollierung aktiviert
Dokumentation
Verarbeitungsverzeichnis aktuell
Technische Dokumentation vorhanden
Datenschutzfolgenabschätzung durchgeführt

Code-Review-Checkliste für DSGVO

" ❌ NICHT DSGVO-konform
DATA: gv_customer_ssn TYPE string. " Sensible Daten global
" ✅ DSGVO-konform
DATA(lo_customer) = zcl_customer_handler=>get_instance( iv_customer_id ).
DATA(lv_masked_ssn) = lo_customer->get_masked_ssn( ).
" ❌ NICHT DSGVO-konform - Logging mit personenbezogenen Daten
MESSAGE |Kunde { ls_customer-name } hat Bestellung { lv_order_id }| TYPE 'I'.
" ✅ DSGVO-konform - Keine personenbezogenen Daten im Log
MESSAGE |Bestellung { lv_order_id } erstellt| TYPE 'I'.
" ❌ NICHT DSGVO-konform - Unbegrenzter Export
SELECT * FROM zcustomer INTO TABLE @DATA(lt_all_customers).
" ✅ DSGVO-konform - Berechtigungsprüfung und Protokollierung
IF lo_auth->check_export_authorization( ) = abap_true.
lo_audit->log_data_export( iv_reason = 'CUSTOMER_REQUEST' ).
SELECT * FROM zcustomer WHERE customer_id = @iv_customer_id
INTO TABLE @DATA(lt_customer_data).
ENDIF.

Automatisierte DSGVO-Prüfungen mit ATC

" Custom ATC-Check für DSGVO-Compliance
" Prüft auf:
" - Hardcodierte personenbezogene Daten
" - Fehlende Berechtigungsprüfungen
" - Ungeschützte Exporte
" - Fehlende Protokollierung

Best Practices

1. Privacy by Design

" Datenminimierung bereits im Datenmodell
define table zcustomer_minimal {
key customer_id : sysuuid_x16; -- Pseudonym statt Klarname
display_name : abap.char(100); -- Optional
email_hash : abap.char(64); -- Gehashte E-Mail
created_at : timestampl;
" KEINE: Geburtsdatum, Adresse, Telefon - wenn nicht benötigt
}

2. Standardmäßiger Datenschutz

" Default: Minimale Datenverarbeitung
METHOD constructor.
" Consent wird NICHT automatisch angenommen
me->consent_status = gc_consent_status-not_given.
" Marketing ist NICHT standardmäßig aktiviert
me->marketing_enabled = abap_false.
" Daten werden NICHT unbegrenzt gespeichert
me->retention_date = cl_abap_context_info=>get_system_date( ) + 365.
ENDMETHOD.

3. Verschlüsselung

" Sensible Daten verschlüsselt speichern
METHOD encrypt_pii_data.
TRY.
DATA(lo_crypto) = cl_abap_symmetric_cipher=>get_aes256( ).
rv_encrypted = lo_crypto->encrypt(
if_data = cl_abap_codepage=>convert_to( iv_plain_text )
if_key = get_encryption_key( ) ).
CATCH cx_abap_symmetric_cipher.
" Fehlerbehandlung
ENDTRY.
ENDMETHOD.

Zusammenfassung

DSGVO-AnforderungABAP Cloud Lösung
LöschrechtRAP Action + Deletion Service
AnonymisierungAnonymization Service
AuskunftsrechtData Export als JSON
Consent ManagementConsent Service + CDS Views
AufbewahrungILM Integration
ProtokollierungApplication Logging

DSGVO-Compliance erfordert eine durchgängige Berücksichtigung von Datenschutzanforderungen in der gesamten Anwendung – vom Datenmodell über die Geschäftslogik bis zur Benutzeroberfläche.

Verwandte Themen