Add privacy protection features to prevent accidental PII leakage: - PII detection engine supporting emails, phone numbers, SSNs, credit cards - CLI privacy command for scanning files and text - Privacy middleware for API request/response filtering - Suggestion utility for privacy-preserving alternatives Integrates PII scanning into manifest validation for narrative fields.
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
// Package middleware provides privacy enforcement for experiment access control.
|
|
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/auth"
|
|
)
|
|
|
|
// PrivacyLevel defines experiment visibility levels.
|
|
type PrivacyLevel string
|
|
|
|
const (
|
|
// PrivacyPrivate restricts access to owner only.
|
|
PrivacyPrivate PrivacyLevel = "private"
|
|
// PrivacyTeam allows team members to view.
|
|
PrivacyTeam PrivacyLevel = "team"
|
|
// PrivacyPublic allows all authenticated users.
|
|
PrivacyPublic PrivacyLevel = "public"
|
|
// PrivacyAnonymized allows access with PII stripped.
|
|
PrivacyAnonymized PrivacyLevel = "anonymized"
|
|
)
|
|
|
|
// PrivacyEnforcer handles privacy access control.
|
|
type PrivacyEnforcer struct {
|
|
enforceTeams bool
|
|
auditAccess bool
|
|
}
|
|
|
|
// NewPrivacyEnforcer creates a privacy enforcer.
|
|
func NewPrivacyEnforcer(enforceTeams, auditAccess bool) *PrivacyEnforcer {
|
|
return &PrivacyEnforcer{
|
|
enforceTeams: enforceTeams,
|
|
auditAccess: auditAccess,
|
|
}
|
|
}
|
|
|
|
// CanAccess checks if a user can access an experiment.
|
|
func (pe *PrivacyEnforcer) CanAccess(
|
|
ctx context.Context,
|
|
user *auth.User,
|
|
experimentOwner string,
|
|
level string,
|
|
team string,
|
|
) (bool, error) {
|
|
privacyLevel := GetPrivacyLevelFromString(level)
|
|
switch privacyLevel {
|
|
case PrivacyPublic:
|
|
return true, nil
|
|
case PrivacyPrivate:
|
|
return user.Name == experimentOwner || user.Admin, nil
|
|
case PrivacyTeam:
|
|
if user.Name == experimentOwner || user.Admin {
|
|
return true, nil
|
|
}
|
|
if !pe.enforceTeams {
|
|
return true, nil // Teams not enforced, allow access
|
|
}
|
|
// Check if user is in same team
|
|
return pe.isUserInTeam(ctx, user, team)
|
|
case PrivacyAnonymized:
|
|
// Anonymized data is accessible but with PII stripped
|
|
return true, nil
|
|
default:
|
|
return false, fmt.Errorf("unknown privacy level: %s", privacyLevel)
|
|
}
|
|
}
|
|
|
|
func (pe *PrivacyEnforcer) isUserInTeam(ctx context.Context, user *auth.User, team string) (bool, error) {
|
|
// TODO: Implement team membership check
|
|
// This could query a teams database or use JWT claims
|
|
// For now, deny access if teams enforcement is on but no check implemented
|
|
_ = ctx
|
|
_ = user
|
|
_ = team
|
|
return false, nil
|
|
}
|
|
|
|
// GetPrivacyLevelFromString converts string to PrivacyLevel.
|
|
func GetPrivacyLevelFromString(level string) PrivacyLevel {
|
|
switch level {
|
|
case "private":
|
|
return PrivacyPrivate
|
|
case "team":
|
|
return PrivacyTeam
|
|
case "public":
|
|
return PrivacyPublic
|
|
case "anonymized":
|
|
return PrivacyAnonymized
|
|
default:
|
|
return PrivacyPrivate // Default to private for safety
|
|
}
|
|
}
|