fetch_ml/internal/middleware/privacy.go
Jeremie Fraeys 02811c0ffe
fix: resolve TODOs and standardize tests
- Fix duplicate check in security_test.go lint warning
- Mark SHA256 tests as Legacy for backward compatibility
- Convert TODO comments to documentation (task, handlers, privacy)
- Update user_manager_test to use GenerateAPIKey pattern
2026-02-19 15:34:59 -05:00

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) {
// Note: Team membership check not yet implemented.
// Future: query teams database or use JWT claims for verification.
// Currently denies access when team enforcement is enabled.
_ = 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
}
}