Commit c532550
authored
feat(gdpr): add GDPR compliance features (#252)
* feat(audit): add audit log query infrastructure for GDPR export
Add the ability to query audit logs, which is essential for GDPR data
export functionality. The implementation uses an interface-based design
allowing different backends (file, database, Elasticsearch).
- AuditEventDTO: DTO for returning audit event data
- AuditLogQueryService: Interface for querying audit events by user
- FileAuditLogQueryService: Default implementation parsing pipe-delimited logs
The file-based implementation parses the existing audit log format and
supports filtering by user, timestamp, and action type.
Closes #250
* feat(event): add GDPR-related application events
Add events for GDPR lifecycle operations following the existing event
pattern used by UserPreDeleteEvent and OnRegistrationCompleteEvent.
- UserDataExportedEvent: Published after successful data export
- UserDeletedEvent: Published after user deletion (post-transaction)
- ConsentChangedEvent: Published when consent is granted or withdrawn
These events enable consuming applications to react to GDPR operations,
such as updating external systems or triggering notifications.
* feat(gdpr): add GDPR compliance services and types
Implement core GDPR functionality including data export, deletion
orchestration, and consent tracking services.
Core Types:
- ConsentType: Enum of standard consent types (PRIVACY_POLICY, etc.)
- ConsentRecord: DTO for consent grant/withdrawal data
- GdprDataContributor: Interface for apps to contribute export data
- GdprConfig: Configuration properties with user.gdpr.* prefix
- GdprExportDTO: Complete data export response structure
Services:
- GdprExportService: Aggregates user data for GDPR Article 15 export
- GdprDeletionService: Orchestrates GDPR Article 17 deletion with hooks
- ConsentAuditService: Tracks consent changes via audit system
The GdprDataContributor interface allows consuming applications to
include their domain-specific data in exports and clean up during
deletion, making the framework extensible while handling core data.
* feat(api): add GDPR REST API endpoints
Add REST controller with endpoints for GDPR operations at /user/gdpr/*
following the existing UserAPI pattern.
Endpoints:
- GET /user/gdpr/export - Export current user's data as JSON
- POST /user/gdpr/delete - Request account deletion
- POST /user/gdpr/consent - Record consent grant/withdrawal
- GET /user/gdpr/consent - Get current consent status
All endpoints require authentication and return standard JSONResponse
format. When GDPR features are disabled via configuration, endpoints
return 404 Not Found.
* chore: add GDPR configuration and UserDeletedEvent publication
Update existing files to support GDPR functionality:
Configuration (dsspringuserconfig.properties):
- user.gdpr.enabled: Master toggle for GDPR features
- user.gdpr.exportBeforeDeletion: Auto-export before hard delete
- user.gdpr.consentTracking: Enable consent audit events
UserService:
- Publish UserDeletedEvent after successful user deletion
- Captures userId and email before deletion for the event
* fix(security): replace manual JSON parsing with Jackson serialization
Address critical code review finding: fragile string parsing had injection
risk. Now using Jackson ObjectMapper for safe JSON handling.
- Add ConsentExtraData DTO for consent extra data serialization
- Replace buildExtraData() with Jackson writeValueAsString()
- Replace extractConsentType()/extractValue() with Jackson readValue()
- Remove vulnerable escapeJson() helper method
- Optimize consent status/export to process events in single pass
* fix(security): remove PII from log statements
Address critical code review finding: user emails were being logged,
exposing PII. Now using user IDs instead.
- Replace user.getEmail() with user.getId() in all GDPR log statements
- Affected files: GdprAPI, GdprDeletionService
- ConsentAuditService/GdprExportService already updated in prior commit
* fix(security): add input validation to consent customType field
Address critical code review finding: missing validation on customType
could allow injection attacks.
- Add @SiZe(max = 100) to limit custom type length
- Add @pattern for alphanumeric, underscore, hyphen only
- Prevents injection of special characters in consent type names
* perf(audit): use streaming for audit log file queries
Address high-severity code review finding: unbounded memory usage when
reading large audit log files.
- Replace BufferedReader iteration with Files.lines() stream
- Process lines lazily to reduce memory footprint
- Maintain same filtering and sorting behavior
* test(api): add GdprAPI unit tests
Add comprehensive unit tests for GDPR REST API endpoints using
standalone MockMvc with mocked services.
Test cases cover:
- Data export: authenticated, unauthenticated, GDPR disabled
- Account deletion: authenticated, unauthenticated
- Consent recording: valid, custom type, validation errors
- Consent status: authenticated, unauthenticated, tracking disabled
* fix(security): invalidate all user sessions on GDPR account deletion
Use SessionInvalidationService to invalidate ALL user sessions across
all devices/browsers before account deletion, not just the current session.
This prevents:
- Security vulnerabilities from orphaned sessions with deleted user refs
- GDPR non-compliance from user data remaining accessible via active sessions
- Application errors when code tries to access the deleted user entity
Changes:
- Add SessionInvalidationService dependency to GdprAPI
- Update logoutUser() to accept User parameter and call invalidateUserSessions()
- Update GdprAPITest with SessionInvalidationService mock
- Update test to use Jackson 3.x (tools.jackson) for consistency with main code
* fix(gdpr): address PR review feedback for validation and documentation
Address code review feedback from PR #252:
- ConsentRequestDto: Change regex pattern from * to + to prevent empty
strings from passing validation for custom consent types
- GdprDeletionService: Capture user email at start of executeUserDeletion()
before entity deletion to avoid potential entity lifecycle issues when
publishing UserDeletedEvent
- FileAuditLogQueryService: Enhance JavaDoc with detailed performance
warning about file-scanning limitations (<50MB, <100K events) and
recommendations for high-volume production deployments
* fix(gdpr): address additional PR review feedback
Apply fixes from second round of code review:
Security/Compliance:
- Sanitize custom consent type names in logs to prevent PII exposure
(log "CUSTOM" instead of actual custom type name)
Performance/Stability:
- Add configurable maxQueryResults limit to AuditConfig (default 10000)
to prevent unbounded memory usage when querying large audit logs
- Apply limit in FileAuditLogQueryService.findByUser() stream
Code Quality:
- Inject Spring-configured ObjectMapper via constructor in
GdprExportService and ConsentAuditService instead of creating new
instances (respects application's Jackson configuration)
- Update tests with @SPY ObjectMapper for proper injection
Configuration:
- Add user.audit.maxQueryResults property to dsspringuserconfig.properties
* fix(gdpr): resolve Codex-identified bugs in audit and consent handling
Address 3 bugs identified by Codex code review:
[P1] Fix audit timestamp parsing for zone-less dates
- FileAuditLogQueryService.parseTimestamp() now tries LocalDateTime.parse()
after ZonedDateTime.parse() fails, handling MessageFormat output like
"Jan 16, 2025, 2:03:04 PM" that lacks timezone info
- Converts LocalDateTime to Instant using system default zone
- Fixes null timestamps in all audit events
[P2] Clear withdrawnAt when consent is re-granted
- GdprExportService.exportConsents() now sets withdrawnAt=null when
updating existing consent record with a grant
- Fixes bug where re-granted consents appeared inactive
[P3] Preserve user identity in GDPR deletion audit event
- GdprAPI.deleteAccount() now passes user object to logAuditEvent()
instead of null, preserving user ID/email in audit trail
- User is still in memory at this point before logout
* fix(gdpr): address final PR review feedback
- Add JavaDoc warning in GdprDeletionService about contributor
transaction safety (external API calls could cause partial deletion)
- Consolidate ConsentAuditService to use shared UserUtils.getClientIP()
- Fix session creation in GdprAPI audit logging (use getSession(false))
- Add rate limiting documentation to export and delete endpoints
- Add defensive parsing in FileAuditLogQueryService for unescaped pipes
* docs(gdpr): add GDPR documentation and disable by default
- Change user.gdpr.enabled default to false for backward compatibility
- Add GDPR features to README.md Features list
- Add comprehensive GDPR Compliance section to README.md with:
- Configuration instructions
- Data export (Right of Access) documentation
- Account deletion (Right to be Forgotten) documentation
- Consent management API documentation
- GdprDataContributor extensibility guide
- GDPR events reference
- Add GDPR configuration section to CONFIG.md
- Add user.audit.maxQueryResults to CONFIG.md1 parent 652ec69 commit c532550
29 files changed
Lines changed: 4600 additions & 0 deletions
File tree
- src
- main
- java/com/digitalsanctuary/spring/user
- api
- audit
- dto
- event
- gdpr
- service
- resources/config
- test/java/com/digitalsanctuary/spring/user
- api
- audit
- event
- gdpr
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
41 | 61 | | |
42 | 62 | | |
43 | 63 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
54 | 61 | | |
55 | 62 | | |
56 | 63 | | |
| |||
92 | 99 | | |
93 | 100 | | |
94 | 101 | | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
95 | 109 | | |
96 | 110 | | |
97 | 111 | | |
| |||
747 | 761 | | |
748 | 762 | | |
749 | 763 | | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
750 | 940 | | |
751 | 941 | | |
752 | 942 | | |
| |||
0 commit comments