Files
GoMFT/internal/db/middleware/encryption_middleware.go
T
StarFleetCPTN 31871bd16e feat: Implement storage provider management functionality
- Added new routes and handlers for managing storage providers, including creation, editing, and deletion.
- Introduced a new StorageProvider form component for user input.
- Enhanced the database schema to support storage provider references in transfer configurations.
- Implemented encryption for sensitive fields in storage provider data.
- Added tests for storage provider API endpoints and integration with the database.
- Updated frontend components to support storage provider selection and testing.
2025-04-16 17:18:53 -07:00

332 lines
9.2 KiB
Go

package middleware
import (
"errors"
"fmt"
"reflect"
"strings"
"github.com/starfleetcptn/gomft/internal/encryption"
"gorm.io/gorm"
)
// EncryptionMiddleware handles automatic encryption and decryption of model fields
type EncryptionMiddleware struct {
encryptor *encryption.CredentialEncryptor
enabled bool
}
// NewEncryptionMiddleware creates a new middleware instance for encrypting/decrypting fields
func NewEncryptionMiddleware() (*EncryptionMiddleware, error) {
// Get the global credential encryptor
encryptor, err := encryption.GetGlobalCredentialEncryptor()
if err != nil {
return nil, fmt.Errorf("failed to initialize encryption middleware: %w", err)
}
return &EncryptionMiddleware{
encryptor: encryptor,
enabled: true,
}, nil
}
// Enable turns on automatic encryption/decryption
func (m *EncryptionMiddleware) Enable() {
m.enabled = true
}
// Disable turns off automatic encryption/decryption
func (m *EncryptionMiddleware) Disable() {
m.enabled = false
}
// IsEnabled returns whether the middleware is enabled
func (m *EncryptionMiddleware) IsEnabled() bool {
return m.enabled
}
// RegisterHooks registers the encryption/decryption hooks with the GORM instance
func (m *EncryptionMiddleware) RegisterHooks(db *gorm.DB) {
// Register BeforeSave hook to encrypt sensitive fields
db.Callback().Create().Before("gorm:create").Register("encrypt_before_create", m.encryptBeforeSave)
db.Callback().Update().Before("gorm:update").Register("encrypt_before_update", m.encryptBeforeSave)
// Register AfterFind hook to decrypt sensitive fields
db.Callback().Query().After("gorm:after_query").Register("decrypt_after_find", m.decryptAfterFind)
}
// encryptBeforeSave encrypts sensitive fields before saving to the database
func (m *EncryptionMiddleware) encryptBeforeSave(db *gorm.DB) {
if !m.enabled {
return
}
// Get the model value
value := db.Statement.ReflectValue
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
// Skip if the value is not a struct
if value.Kind() != reflect.Struct {
return
}
// Process the model
if err := m.processModelForEncryption(value); err != nil {
db.AddError(fmt.Errorf("encryption middleware error: %w", err))
}
}
// decryptAfterFind decrypts sensitive fields after retrieving from the database
func (m *EncryptionMiddleware) decryptAfterFind(db *gorm.DB) {
if !m.enabled {
return
}
// Get the model value
value := db.Statement.ReflectValue
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
// Handle slice of models
if value.Kind() == reflect.Slice {
for i := 0; i < value.Len(); i++ {
item := value.Index(i)
if item.Kind() == reflect.Ptr {
item = item.Elem()
}
if item.Kind() == reflect.Struct {
if err := m.processModelForDecryption(item); err != nil {
db.AddError(fmt.Errorf("decryption middleware error [index %d]: %w", i, err))
return
}
}
}
return
}
// Skip if the value is not a struct
if value.Kind() != reflect.Struct {
return
}
// Process the model
if err := m.processModelForDecryption(value); err != nil {
db.AddError(fmt.Errorf("decryption middleware error: %w", err))
}
}
// processModelForEncryption encrypts sensitive fields in a model
func (m *EncryptionMiddleware) processModelForEncryption(value reflect.Value) error {
modelType := value.Type()
// Special handling for StorageProvider type
if modelType.Name() == "StorageProvider" {
return m.encryptStorageProvider(value)
}
// Generic handling for models with encryptable fields
for i := 0; i < modelType.NumField(); i++ {
field := modelType.Field(i)
// Check if field requires encryption based on its name
fieldName := field.Name
if requiresEncryption, credType := encryption.RequiresEncryption(fieldName); requiresEncryption {
// Get the field value
fieldValue := value.Field(i)
if !fieldValue.CanInterface() || !fieldValue.CanSet() {
continue
}
// Get string value
strValue, ok := fieldValue.Interface().(string)
if !ok || strValue == "" {
continue
}
// If already encrypted, skip
if m.encryptor.IsEncrypted(strValue) {
continue
}
// Encrypt the field
encryptedValue, err := m.encryptor.Encrypt(strValue, credType)
if err != nil {
return fmt.Errorf("failed to encrypt field %s: %w", fieldName, err)
}
// Find the corresponding encrypted field
encryptedFieldName := "Encrypted" + fieldName
encryptedField := value.FieldByName(encryptedFieldName)
// If encrypted field exists and can be set, set it
if encryptedField.IsValid() && encryptedField.CanSet() {
encryptedField.SetString(encryptedValue)
// If the original field is marked with gorm:"-", we should clear it to prevent leaking it
if field.Tag.Get("gorm") == "-" {
fieldValue.SetString("")
}
}
}
}
return nil
}
// processModelForDecryption decrypts encrypted fields in a model
func (m *EncryptionMiddleware) processModelForDecryption(value reflect.Value) error {
modelType := value.Type()
// Special handling for StorageProvider type
if modelType.Name() == "StorageProvider" {
return m.decryptStorageProvider(value)
}
// Generic handling for models with encrypted fields
for i := 0; i < modelType.NumField(); i++ {
field := modelType.Field(i)
// Look for encrypted fields based on naming pattern
fieldName := field.Name
if strings.HasPrefix(fieldName, "Encrypted") {
originalFieldName := strings.TrimPrefix(fieldName, "Encrypted")
// Get the encrypted field value
encryptedFieldValue := value.Field(i)
if !encryptedFieldValue.CanInterface() {
continue
}
// Get encrypted string value
encryptedValue, ok := encryptedFieldValue.Interface().(string)
if !ok || encryptedValue == "" {
continue
}
// Decrypt the field
decryptedValue, err := m.encryptor.DecryptField(encryptedValue)
if err != nil {
// Log the error but continue
fmt.Printf("Warning: failed to decrypt field %s: %v\n", fieldName, err)
continue
}
// Find the corresponding original field
originalField := value.FieldByName(originalFieldName)
// If original field exists and can be set, set it
if originalField.IsValid() && originalField.CanSet() {
originalField.SetString(decryptedValue)
}
}
}
return nil
}
// encryptStorageProvider handles encryption for StorageProvider model fields
func (m *EncryptionMiddleware) encryptStorageProvider(value reflect.Value) error {
// Check if model implements GetSensitiveFields method
modelInterface := value.Addr().Interface()
// Type assertion to access the GetSensitiveFields method
model, ok := modelInterface.(interface {
GetSensitiveFields() map[string]string
})
if !ok {
return errors.New("StorageProvider model does not implement GetSensitiveFields")
}
// Get sensitive fields that need encryption
sensitiveFields := model.GetSensitiveFields()
// Encrypt each sensitive field
for fieldName, fieldValue := range sensitiveFields {
if fieldValue == "" {
continue
}
// Skip already encrypted values
if m.encryptor.IsEncrypted(fieldValue) {
continue
}
// Determine the credential type based on field name
_, credType := encryption.RequiresEncryption(fieldName)
// Encrypt the value
encryptedValue, err := m.encryptor.Encrypt(fieldValue, credType)
if err != nil {
return fmt.Errorf("failed to encrypt StorageProvider field %s: %w", fieldName, err)
}
// Find the corresponding encrypted field
encryptedFieldName := "Encrypted" + fieldName
encryptedField := value.FieldByName(encryptedFieldName)
// Set the encrypted value
if encryptedField.IsValid() && encryptedField.CanSet() {
encryptedField.SetString(encryptedValue)
// Clear the original field if it shouldn't be stored
originalField := value.FieldByName(fieldName)
if originalField.IsValid() && originalField.CanSet() {
// Find the field in the struct type to check its gorm tag
modelType := reflect.TypeOf(model).Elem()
if field, found := modelType.FieldByName(fieldName); found && field.Tag.Get("gorm") == "-" {
originalField.SetString("")
}
}
}
}
return nil
}
// decryptStorageProvider handles decryption for StorageProvider model fields
func (m *EncryptionMiddleware) decryptStorageProvider(value reflect.Value) error {
// Fields to decrypt
encryptedFields := []string{
"EncryptedPassword",
"EncryptedSecretKey",
"EncryptedClientSecret",
"EncryptedRefreshToken",
}
// Process each encrypted field
for _, fieldName := range encryptedFields {
encryptedField := value.FieldByName(fieldName)
if !encryptedField.IsValid() || !encryptedField.CanInterface() {
continue
}
// Get encrypted value
encryptedValue, ok := encryptedField.Interface().(string)
if !ok || encryptedValue == "" {
continue
}
// Decrypt value
decryptedValue, err := m.encryptor.DecryptField(encryptedValue)
if err != nil {
// Log warning but continue with other fields
fmt.Printf("Warning: failed to decrypt StorageProvider field %s: %v\n", fieldName, err)
continue
}
// Set decrypted value to the original field
originalFieldName := strings.TrimPrefix(fieldName, "Encrypted")
originalField := value.FieldByName(originalFieldName)
if originalField.IsValid() && originalField.CanSet() {
originalField.SetString(decryptedValue)
}
}
return nil
}