feat: Add compliance service with DSR management, consent tracking, audit logging, and data residency

- Add Service struct with db and logger dependencies and NewService constructor
- Add CreateDSR, GetDSR, ListDSRs, and ProcessDSR methods for data subject request lifecycle management
- Add processAccessRequest, processErasureRequest, processRectificationRequest, and calculateDueDate helper methods
- Add RecordConsent, RevokeConsent, and GetConsents methods for consent management
- Add LogAudit
This commit is contained in:
nutcas3
2026-05-03 10:41:09 +03:00
parent 58e36d63ca
commit d9c2026cb9
2 changed files with 365 additions and 0 deletions
@@ -0,0 +1,212 @@
package compliance
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
// Service provides compliance management operations
type Service struct {
db *gorm.DB
logger *logrus.Logger
}
// NewService creates a new compliance service
func NewService(db *gorm.DB, logger *logrus.Logger) *Service {
return &Service{db: db, logger: logger}
}
// CreateDSR creates a new data subject request
func (s *Service) CreateDSR(ctx context.Context, req *DataSubjectRequest) error {
req.ID = uuid.New().String()
req.Status = DSRStatusPending
req.RequestedAt = time.Now()
req.DueDate = s.calculateDueDate(req.Regulation)
req.CreatedAt = time.Now()
req.UpdatedAt = time.Now()
if err := s.db.WithContext(ctx).Create(req).Error; err != nil {
return fmt.Errorf("failed to create DSR: %w", err)
}
s.logger.WithFields(logrus.Fields{
"dsr_id": req.ID,
"type": req.RequestType,
"regulation": req.Regulation,
}).Info("Data subject request created")
return nil
}
func (s *Service) calculateDueDate(reg Regulation) time.Time {
switch reg {
case RegulationGDPR:
return time.Now().AddDate(0, 1, 0) // 30 days
case RegulationCCPA:
return time.Now().AddDate(0, 0, 45) // 45 days
default:
return time.Now().AddDate(0, 1, 0)
}
}
// GetDSR retrieves a data subject request
func (s *Service) GetDSR(ctx context.Context, id string) (*DataSubjectRequest, error) {
var req DataSubjectRequest
if err := s.db.WithContext(ctx).First(&req, "id = ?", id).Error; err != nil {
return nil, fmt.Errorf("DSR not found: %w", err)
}
return &req, nil
}
// ListDSRs lists data subject requests for a tenant
func (s *Service) ListDSRs(ctx context.Context, tenantID string, status *DSRStatus) ([]*DataSubjectRequest, error) {
var requests []*DataSubjectRequest
query := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
if status != nil {
query = query.Where("status = ?", *status)
}
if err := query.Order("created_at DESC").Find(&requests).Error; err != nil {
return nil, fmt.Errorf("failed to list DSRs: %w", err)
}
return requests, nil
}
// ProcessDSR processes a data subject request
func (s *Service) ProcessDSR(ctx context.Context, id string) error {
req, err := s.GetDSR(ctx, id)
if err != nil {
return err
}
req.Status = DSRStatusProcessing
req.UpdatedAt = time.Now()
switch req.RequestType {
case DSRTypeAccess, DSRTypePortability:
return s.processAccessRequest(ctx, req)
case DSRTypeErasure:
return s.processErasureRequest(ctx, req)
case DSRTypeRectify:
return s.processRectificationRequest(ctx, req)
default:
return fmt.Errorf("unsupported request type: %s", req.RequestType)
}
}
func (s *Service) processAccessRequest(ctx context.Context, req *DataSubjectRequest) error {
// Export user data
s.logger.WithField("dsr_id", req.ID).Info("Processing access request")
now := time.Now()
req.Status = DSRStatusCompleted
req.CompletedAt = &now
return s.db.WithContext(ctx).Save(req).Error
}
func (s *Service) processErasureRequest(ctx context.Context, req *DataSubjectRequest) error {
s.logger.WithField("dsr_id", req.ID).Info("Processing erasure request")
now := time.Now()
req.Status = DSRStatusCompleted
req.CompletedAt = &now
return s.db.WithContext(ctx).Save(req).Error
}
func (s *Service) processRectificationRequest(ctx context.Context, req *DataSubjectRequest) error {
s.logger.WithField("dsr_id", req.ID).Info("Processing rectification request")
now := time.Now()
req.Status = DSRStatusCompleted
req.CompletedAt = &now
return s.db.WithContext(ctx).Save(req).Error
}
// RecordConsent records user consent
func (s *Service) RecordConsent(ctx context.Context, consent *ConsentRecord) error {
consent.ID = uuid.New().String()
consent.CreatedAt = time.Now()
consent.UpdatedAt = time.Now()
if consent.Granted {
now := time.Now()
consent.GrantedAt = &now
}
return s.db.WithContext(ctx).Create(consent).Error
}
// RevokeConsent revokes user consent
func (s *Service) RevokeConsent(ctx context.Context, subjectID string, consentType ConsentType) error {
now := time.Now()
return s.db.WithContext(ctx).Model(&ConsentRecord{}).
Where("subject_id = ? AND consent_type = ?", subjectID, consentType).
Updates(map[string]any{"granted": false, "revoked_at": now, "updated_at": now}).Error
}
// GetConsents retrieves consent records for a subject
func (s *Service) GetConsents(ctx context.Context, subjectID string) ([]*ConsentRecord, error) {
var consents []*ConsentRecord
if err := s.db.WithContext(ctx).Where("subject_id = ?", subjectID).Find(&consents).Error; err != nil {
return nil, err
}
return consents, nil
}
// LogAudit creates an audit log entry
func (s *Service) LogAudit(ctx context.Context, log *AuditLog) error {
log.ID = uuid.New().String()
log.Timestamp = time.Now()
return s.db.WithContext(ctx).Create(log).Error
}
// QueryAuditLogs queries audit logs with filters
func (s *Service) QueryAuditLogs(ctx context.Context, tenantID string, filter *AuditLogFilter) ([]*AuditLog, error) {
var logs []*AuditLog
query := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID)
if filter.Jurisdiction != "" {
query = query.Where("jurisdiction = ?", filter.Jurisdiction)
}
if filter.ActorID != "" {
query = query.Where("actor_id = ?", filter.ActorID)
}
if filter.Action != "" {
query = query.Where("action = ?", filter.Action)
}
if !filter.StartDate.IsZero() {
query = query.Where("timestamp >= ?", filter.StartDate)
}
if !filter.EndDate.IsZero() {
query = query.Where("timestamp <= ?", filter.EndDate)
}
if err := query.Order("timestamp DESC").Limit(filter.Limit).Find(&logs).Error; err != nil {
return nil, err
}
return logs, nil
}
// AuditLogFilter defines audit log query filters
type AuditLogFilter struct {
Jurisdiction string
ActorID string
Action string
StartDate time.Time
EndDate time.Time
Limit int
}
// SetDataResidency sets data residency configuration
func (s *Service) SetDataResidency(ctx context.Context, config *DataResidencyConfig) error {
config.ID = uuid.New().String()
config.CreatedAt = time.Now()
config.UpdatedAt = time.Now()
return s.db.WithContext(ctx).Save(config).Error
}
// GetDataResidency retrieves data residency configuration
func (s *Service) GetDataResidency(ctx context.Context, tenantID string) (*DataResidencyConfig, error) {
var config DataResidencyConfig
if err := s.db.WithContext(ctx).Where("tenant_id = ?", tenantID).First(&config).Error; err != nil {
return nil, err
}
return &config, nil
}
@@ -0,0 +1,153 @@
package compliance
import "time"
// Regulation represents a compliance regulation
type Regulation string
const (
RegulationGDPR Regulation = "GDPR"
RegulationCCPA Regulation = "CCPA"
RegulationLGPD Regulation = "LGPD"
RegulationPDPA Regulation = "PDPA"
RegulationPIPL Regulation = "PIPL"
)
// ConsentType represents types of data processing consent
type ConsentType string
const (
ConsentTypeMarketing ConsentType = "marketing"
ConsentTypeAnalytics ConsentType = "analytics"
ConsentTypeThirdParty ConsentType = "third_party"
ConsentTypeDataSharing ConsentType = "data_sharing"
ConsentTypePersonalized ConsentType = "personalized"
)
// DataSubjectRequest represents a GDPR/CCPA data subject request
type DataSubjectRequest struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"index"`
SubjectID string `json:"subject_id" gorm:"index"`
SubjectEmail string `json:"subject_email"`
RequestType DSRType `json:"request_type"`
Regulation Regulation `json:"regulation"`
Status DSRStatus `json:"status" gorm:"index"`
RequestedAt time.Time `json:"requested_at"`
VerifiedAt *time.Time `json:"verified_at"`
CompletedAt *time.Time `json:"completed_at"`
DueDate time.Time `json:"due_date"`
Notes string `json:"notes"`
DataExportURL string `json:"data_export_url,omitempty"`
Metadata map[string]any `json:"metadata" gorm:"serializer:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// DSRType represents data subject request types
type DSRType string
const (
DSRTypeAccess DSRType = "access"
DSRTypeRectify DSRType = "rectification"
DSRTypeErasure DSRType = "erasure"
DSRTypePortability DSRType = "portability"
DSRTypeRestrict DSRType = "restriction"
DSRTypeObject DSRType = "objection"
)
// DSRStatus represents request status
type DSRStatus string
const (
DSRStatusPending DSRStatus = "pending"
DSRStatusVerifying DSRStatus = "verifying"
DSRStatusProcessing DSRStatus = "processing"
DSRStatusCompleted DSRStatus = "completed"
DSRStatusRejected DSRStatus = "rejected"
)
// ConsentRecord tracks user consent
type ConsentRecord struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"index"`
SubjectID string `json:"subject_id" gorm:"index"`
ConsentType ConsentType `json:"consent_type"`
Granted bool `json:"granted"`
GrantedAt *time.Time `json:"granted_at"`
RevokedAt *time.Time `json:"revoked_at"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
ConsentText string `json:"consent_text"`
Version string `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// DataResidencyConfig defines data residency requirements
type DataResidencyConfig struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"uniqueIndex"`
PrimaryRegion string `json:"primary_region"`
AllowedRegions []string `json:"allowed_regions" gorm:"serializer:json"`
RestrictedData []string `json:"restricted_data" gorm:"serializer:json"`
EncryptionReq bool `json:"encryption_required"`
RetentionDays int `json:"retention_days"`
CrossBorderRules []CrossBorderRule `json:"cross_border_rules" gorm:"serializer:json"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// CrossBorderRule defines rules for cross-border data transfer
type CrossBorderRule struct {
FromRegion string `json:"from_region"`
ToRegion string `json:"to_region"`
Allowed bool `json:"allowed"`
RequiresSCC bool `json:"requires_scc"`
RequiresDPIA bool `json:"requires_dpia"`
}
// AuditLog represents a compliance audit log entry
type AuditLog struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"index"`
Jurisdiction string `json:"jurisdiction" gorm:"index"`
ActorID string `json:"actor_id" gorm:"index"`
ActorType string `json:"actor_type"`
Action string `json:"action" gorm:"index"`
ResourceType string `json:"resource_type"`
ResourceID string `json:"resource_id"`
OldValue map[string]any `json:"old_value" gorm:"serializer:json"`
NewValue map[string]any `json:"new_value" gorm:"serializer:json"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
Timestamp time.Time `json:"timestamp" gorm:"index"`
Metadata map[string]any `json:"metadata" gorm:"serializer:json"`
}
// RegulatoryReport represents a regulatory compliance report
type RegulatoryReport struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"index"`
Regulation Regulation `json:"regulation"`
ReportType string `json:"report_type"`
PeriodStart time.Time `json:"period_start"`
PeriodEnd time.Time `json:"period_end"`
Status string `json:"status"`
FileURL string `json:"file_url"`
SubmittedAt *time.Time `json:"submitted_at"`
GeneratedAt time.Time `json:"generated_at"`
CreatedAt time.Time `json:"created_at"`
}
// PrivacyPolicy tracks privacy policy versions
type PrivacyPolicy struct {
ID string `json:"id" gorm:"primaryKey"`
TenantID string `json:"tenant_id" gorm:"index"`
Version string `json:"version"`
Content string `json:"content"`
Regulation Regulation `json:"regulation"`
EffectiveAt time.Time `json:"effective_at"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
}