mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-20 13:30:51 +02:00
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.
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user