mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 02:52:53 +00:00
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:
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user