feat: Integrazione completa gestione credenziali per database e REST API con supporto SAP B1 e Salesforce

NUOVE FUNZIONALITÀ:
- Aggiunto modulo CredentialManager per gestione centralizzata credenziali
- Implementata UI Blazor per gestione credenziali (CredentialManagement.razor)
- Supporto completo per credenziali database (SQL Server, MySQL, PostgreSQL, Oracle, SQLite, DB2, SAP HANA)
- Gestione unificata REST API con supporto specifico per SAP B1 Service Layer e Salesforce
- Test reali di connessione per database, SAP B1 e Salesforce OAuth2
- Selezione dinamica tipo servizio REST (Generico, SAP B1, Salesforce) con campi specifici
- Persistenza sicura di credenziali con crittografia password e campi sensibili

COMPONENTI AGGIUNTI:
- CredentialManager/Models/: CredentialEntity, CredentialModels (DatabaseCredential, RestApiCredential, SapB1ServiceLayerCredential, SalesforceCredential)
- CredentialManager/Services/: CredentialService, EncryptionService, DatabaseInitializer
- CredentialManager/Data/: CredentialDbContext con Entity Framework
- DataConnection/CredentialManagement/: Interfacce e servizi di integrazione
- Data_Coupler/Pages/CredentialManagement.razor: UI completa per gestione credenziali

MIGLIORAMENTI UI:
- Form dinamica per REST API con campi specifici per tipo servizio
- Validazione campi obbligatori per Salesforce (ClientId, ClientSecret, SecurityToken)
- Test connessione in tempo reale dalla modale di inserimento/modifica
- Rimozione sezioni separate per SAP B1 e Salesforce (ora unificate in REST API)
- Gestione stato loading durante operazioni async

PERSISTENZA AVANZATA:
- Campo RestServiceType aggiunto a CredentialEntity con migrazione automatica
- Serializzazione campi specifici Salesforce/SAP B1 in AdditionalParameters JSON
- Mapping bidirezionale tra entità database e modelli business
- Gestione nullability e conversioni tipo sicure

SICUREZZA:
- Crittografia AES-256 per password e token sensibili
- Gestione sicura ConnectionString database
- Validazione input e sanitizzazione dati

TESTING E CONNETTIVITÀ:
- Test autenticazione reale SAP B1 Service Layer
- Test OAuth2 Salesforce con supporto Connected App
- Test connettività database multi-provider
- Logging dettagliato per debugging e monitoraggio

CONFIGURAZIONE:
- Dependency injection per tutti i servizi
- Configurazione Entity Framework con SQLite
- Tasks VS Code per build e run
- Gestione connection string centralizzata

CORREZIONI:
- Risolti errori nullability in CredentialService
- Aggiunto using Microsoft.JSInterop per IJSRuntime
- Fix compilazione e warning

Files modificati: 35+ file tra nuovi e aggiornati
This commit is contained in:
2025-06-17 01:43:17 +02:00
parent 09d07db2ba
commit c22b4a2613
38 changed files with 5002 additions and 27 deletions
View File
+12 -1
View File
@@ -4,6 +4,17 @@
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</PropertyGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="9.0.0" />
</ItemGroup>
</Project>
@@ -0,0 +1,129 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using CredentialManager.Data;
using CredentialManager.Services;
namespace CredentialManager;
/// <summary>
/// Classe per la configurazione del CredentialManager
/// </summary>
public static class CredentialManagerConfiguration
{
/// <summary>
/// Registra i servizi del CredentialManager
/// </summary>
/// <param name="services">La collezione di servizi</param>
/// <param name="databasePath">Il percorso del database SQLite (opzionale, default: credentials.db)</param>
/// <returns>La collezione di servizi</returns>
public static IServiceCollection AddCredentialManager(
this IServiceCollection services,
string? databasePath = null)
{
// Imposta il percorso predefinito se non specificato
databasePath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"CredentialManager", "credentials.db");
// Assicurati che la directory esista
var directory = Path.GetDirectoryName(databasePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
// Registra il DbContext
services.AddDbContext<CredentialDbContext>(options =>
options.UseSqlite($"Data Source={databasePath}"));
// Registra i servizi
services.AddScoped<IEncryptionService, EncryptionService>();
services.AddScoped<ICredentialService, CredentialService>();
services.AddScoped<IDatabaseInitializer, DatabaseInitializer>();
return services;
}
/// <summary>
/// Inizializza il database del CredentialManager
/// </summary>
/// <param name="serviceProvider">Il provider di servizi</param>
/// <returns>Task completato</returns>
public static async Task InitializeCredentialManagerAsync(this IServiceProvider serviceProvider)
{
using var scope = serviceProvider.CreateScope();
var initializer = scope.ServiceProvider.GetRequiredService<IDatabaseInitializer>();
await initializer.InitializeAsync();
}
}
/// <summary>
/// Factory per creare un'istanza standalone del CredentialManager
/// </summary>
public static class CredentialManagerFactory
{
/// <summary>
/// Crea un'istanza standalone del CredentialManager
/// </summary>
/// <param name="databasePath">Il percorso del database SQLite (opzionale)</param>
/// <param name="loggerFactory">Factory per il logging (opzionale)</param>
/// <returns>Un'istanza di ICredentialService</returns>
public static async Task<ICredentialService> CreateAsync(
string? databasePath = null,
ILoggerFactory? loggerFactory = null)
{
// Configurazione dei servizi
var services = new ServiceCollection();
// Aggiungi logging se fornito
if (loggerFactory != null)
{
services.AddSingleton(loggerFactory);
services.AddLogging();
}
else
{
services.AddLogging(builder => builder.AddConsole());
}
// Aggiungi CredentialManager
services.AddCredentialManager(databasePath);
// Costruisci il provider di servizi
var serviceProvider = services.BuildServiceProvider();
// Inizializza il database
await serviceProvider.InitializeCredentialManagerAsync();
// Restituisci il servizio
return serviceProvider.GetRequiredService<ICredentialService>();
}
/// <summary>
/// Crea un'istanza standalone del CredentialManager con configurazione personalizzata
/// </summary>
/// <param name="configureServices">Azione per configurare servizi aggiuntivi</param>
/// <param name="databasePath">Il percorso del database SQLite (opzionale)</param>
/// <returns>Il provider di servizi configurato</returns>
public static async Task<IServiceProvider> CreateServiceProviderAsync(
Action<IServiceCollection>? configureServices = null,
string? databasePath = null)
{
var services = new ServiceCollection();
// Configurazione di base
services.AddLogging(builder => builder.AddConsole());
services.AddCredentialManager(databasePath);
// Configurazione personalizzata
configureServices?.Invoke(services);
// Costruisci il provider
var serviceProvider = services.BuildServiceProvider();
// Inizializza il database
await serviceProvider.InitializeCredentialManagerAsync();
return serviceProvider;
}
}
@@ -0,0 +1,88 @@
using Microsoft.EntityFrameworkCore;
using CredentialManager.Models;
namespace CredentialManager.Data;
/// <summary>
/// DbContext per la gestione delle credenziali
/// </summary>
public class CredentialDbContext : DbContext
{
public DbSet<CredentialEntity> Credentials { get; set; }
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // Configurazione della tabella Credentials
modelBuilder.Entity<CredentialEntity>(entity =>
{
entity.ToTable("Credentials");
entity.HasKey(e => e.Id);
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(100);
entity.Property(e => e.Type)
.IsRequired()
.HasMaxLength(50);
entity.Property(e => e.DatabaseType)
.HasMaxLength(50);
entity.Property(e => e.ConnectionString)
.HasMaxLength(500);
entity.Property(e => e.Host)
.HasMaxLength(200);
entity.Property(e => e.DatabaseName)
.HasMaxLength(100);
entity.Property(e => e.Username)
.HasMaxLength(100);
entity.Property(e => e.EncryptedApiKey)
.HasMaxLength(500);
entity.Property(e => e.EncryptedAuthToken)
.HasMaxLength(500);
entity.Property(e => e.Headers)
.HasMaxLength(2000);
entity.Property(e => e.AdditionalParameters)
.HasMaxLength(2000);
entity.Property(e => e.CreatedBy)
.HasMaxLength(100);
// Valori di default
entity.Property(e => e.CommandTimeout)
.HasDefaultValue(30);
entity.Property(e => e.TimeoutSeconds)
.HasDefaultValue(100);
entity.Property(e => e.IgnoreSslErrors)
.HasDefaultValue(false);
entity.Property(e => e.IsActive)
.HasDefaultValue(true);
// Indici
entity.HasIndex(e => e.Name)
.IsUnique();
entity.HasIndex(e => e.Type);
entity.HasIndex(e => e.DatabaseType);
entity.HasIndex(e => e.IsActive);
});
}
}
@@ -0,0 +1,212 @@
using CredentialManager.Models;
using CredentialManager.Services;
namespace CredentialManager.Integration;
/// <summary>
/// Metodi di utilità per l'integrazione con DataConnection
/// </summary>
public static class DataConnectionHelper
{
/// <summary>
/// Converte una DatabaseCredential in una stringa di connessione
/// </summary>
/// <param name="credential">La credenziale database</param>
/// <returns>Stringa di connessione pronta per l'uso</returns>
public static string ToConnectionString(this DatabaseCredential credential)
{
return ConnectionStringBuilder.BuildConnectionString(credential);
}
/// <summary>
/// Crea le opzioni per RestServiceOptions dal progetto DataConnection
/// </summary>
/// <param name="credential">La credenziale REST API</param>
/// <returns>Oggetto con le opzioni per REST service</returns>
public static object ToRestServiceOptions(this RestApiCredential credential)
{
return new
{
BaseUrl = credential.BaseUrl,
ApiKey = credential.ApiKey,
Username = credential.Username,
Password = credential.Password,
AuthToken = credential.AuthToken,
TimeoutSeconds = credential.TimeoutSeconds,
IgnoreSslErrors = credential.IgnoreSslErrors
};
}
/// <summary>
/// Crea le opzioni per DbManagerOptions dal progetto DataConnection
/// </summary>
/// <param name="credential">La credenziale database</param>
/// <returns>Oggetto con le opzioni per DB manager</returns>
public static object ToDbManagerOptions(this DatabaseCredential credential)
{
return new
{
ServerConnectionString = credential.ToConnectionString(),
DatabaseName = credential.DatabaseName,
DatabaseType = credential.DatabaseType.ToString(),
CommandTimeout = credential.CommandTimeout
};
}
/// <summary>
/// Ottiene la porta predefinita per un tipo di database
/// </summary>
/// <param name="databaseType">Tipo di database</param>
/// <returns>Porta predefinita</returns>
public static int GetDefaultPort(DatabaseType databaseType)
{
return databaseType switch
{
DatabaseType.SqlServer => 1433,
DatabaseType.MySql => 3306,
DatabaseType.PostgreSql => 5432,
DatabaseType.Oracle => 1521,
DatabaseType.DB2 => 50000,
DatabaseType.SapHana => 30015,
DatabaseType.Sqlite => 0, // Non applicabile
_ => 0
};
}
/// <summary>
/// Valida una credenziale database
/// </summary>
/// <param name="credential">La credenziale da validare</param>
/// <returns>Lista di errori di validazione (vuota se valida)</returns>
public static List<string> ValidateDatabaseCredential(DatabaseCredential credential)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(credential.Name))
errors.Add("Il nome della credenziale è obbligatorio");
if (string.IsNullOrWhiteSpace(credential.Host) && credential.DatabaseType != DatabaseType.Sqlite)
errors.Add("L'host è obbligatorio per questo tipo di database");
if (string.IsNullOrWhiteSpace(credential.DatabaseName))
errors.Add("Il nome del database è obbligatorio");
if (string.IsNullOrWhiteSpace(credential.Username) && credential.DatabaseType != DatabaseType.Sqlite)
errors.Add("Il nome utente è obbligatorio per questo tipo di database");
if (string.IsNullOrWhiteSpace(credential.Password) && credential.DatabaseType != DatabaseType.Sqlite)
errors.Add("La password è obbligatoria per questo tipo di database");
if (credential.Port <= 0 && credential.DatabaseType != DatabaseType.Sqlite)
{
// Assegna porta predefinita se non specificata
credential.Port = GetDefaultPort(credential.DatabaseType);
}
return errors;
}
/// <summary>
/// Valida una credenziale REST API
/// </summary>
/// <param name="credential">La credenziale da validare</param>
/// <returns>Lista di errori di validazione (vuota se valida)</returns>
public static List<string> ValidateRestApiCredential(RestApiCredential credential)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(credential.Name))
errors.Add("Il nome della credenziale è obbligatorio");
if (string.IsNullOrWhiteSpace(credential.BaseUrl))
errors.Add("L'URL base è obbligatorio");
else if (!Uri.TryCreate(credential.BaseUrl, UriKind.Absolute, out _))
errors.Add("L'URL base non è valido");
// Almeno uno tra ApiKey, Username/Password, o AuthToken deve essere specificato
var hasApiKey = !string.IsNullOrWhiteSpace(credential.ApiKey);
var hasUserPass = !string.IsNullOrWhiteSpace(credential.Username) && !string.IsNullOrWhiteSpace(credential.Password);
var hasAuthToken = !string.IsNullOrWhiteSpace(credential.AuthToken);
if (!hasApiKey && !hasUserPass && !hasAuthToken)
errors.Add("Deve essere specificato almeno un metodo di autenticazione (API Key, Username/Password, o Auth Token)");
return errors;
}
}
/// <summary>
/// Estensioni per la gestione asincrona delle credenziali
/// </summary>
public static class CredentialServiceExtensions
{
/// <summary>
/// Ottiene una credenziale database validata
/// </summary>
/// <param name="service">Il servizio credenziali</param>
/// <param name="name">Nome della credenziale</param>
/// <returns>Credenziale validata o null se non trovata</returns>
public static async Task<DatabaseCredential?> GetValidatedDatabaseCredentialAsync(
this ICredentialService service, string name)
{
var credential = await service.GetDatabaseCredentialAsync(name);
if (credential == null) return null;
var errors = DataConnectionHelper.ValidateDatabaseCredential(credential);
if (errors.Any())
throw new ArgumentException($"Credenziale non valida: {string.Join(", ", errors)}");
return credential;
}
/// <summary>
/// Ottiene una credenziale REST API validata
/// </summary>
/// <param name="service">Il servizio credenziali</param>
/// <param name="name">Nome della credenziale</param>
/// <returns>Credenziale validata o null se non trovata</returns>
public static async Task<RestApiCredential?> GetValidatedRestApiCredentialAsync(
this ICredentialService service, string name)
{
var credential = await service.GetRestApiCredentialAsync(name);
if (credential == null) return null;
var errors = DataConnectionHelper.ValidateRestApiCredential(credential);
if (errors.Any())
throw new ArgumentException($"Credenziale non valida: {string.Join(", ", errors)}");
return credential;
}
/// <summary>
/// Salva una credenziale database con validazione
/// </summary>
/// <param name="service">Il servizio credenziali</param>
/// <param name="credential">La credenziale da salvare</param>
/// <returns>ID della credenziale salvata</returns>
public static async Task<int> SaveValidatedDatabaseCredentialAsync(
this ICredentialService service, DatabaseCredential credential)
{
var errors = DataConnectionHelper.ValidateDatabaseCredential(credential);
if (errors.Any())
throw new ArgumentException($"Credenziale non valida: {string.Join(", ", errors)}");
return await service.SaveDatabaseCredentialAsync(credential);
}
/// <summary>
/// Salva una credenziale REST API con validazione
/// </summary>
/// <param name="service">Il servizio credenziali</param>
/// <param name="credential">La credenziale da salvare</param>
/// <returns>ID della credenziale salvata</returns>
public static async Task<int> SaveValidatedRestApiCredentialAsync(
this ICredentialService service, RestApiCredential credential)
{
var errors = DataConnectionHelper.ValidateRestApiCredential(credential);
if (errors.Any())
throw new ArgumentException($"Credenziale non valida: {string.Join(", ", errors)}");
return await service.SaveRestApiCredentialAsync(credential);
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddRestServiceType : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "RestServiceType",
table: "Credentials",
type: "TEXT",
maxLength: 50,
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "RestServiceType",
table: "Credentials");
}
}
}
@@ -0,0 +1,72 @@
using System.ComponentModel.DataAnnotations;
namespace CredentialManager.Models;
/// <summary>
/// Entità per memorizzare le credenziali nel database
/// </summary>
public class CredentialEntity
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
[MaxLength(50)]
public string Type { get; set; } = string.Empty; // Database, REST, etc.
[MaxLength(50)]
public string? DatabaseType { get; set; } // SqlServer, MySql, etc.
[MaxLength(500)]
public string? ConnectionString { get; set; }
[MaxLength(200)]
public string? Host { get; set; }
public int? Port { get; set; }
[MaxLength(100)]
public string? DatabaseName { get; set; }
[MaxLength(100)]
public string? Username { get; set; }
/// <summary>
/// Password criptata
/// </summary>
public string? EncryptedPassword { get; set; }
[MaxLength(500)]
public string? EncryptedApiKey { get; set; }
[MaxLength(500)]
public string? EncryptedAuthToken { get; set; }
public int CommandTimeout { get; set; } = 30;
public int TimeoutSeconds { get; set; } = 100;
public bool IgnoreSslErrors { get; set; } = false;
[MaxLength(50)]
public string? RestServiceType { get; set; } // Generic, SapB1ServiceLayer, Salesforce
[MaxLength(2000)]
public string? Headers { get; set; } // JSON serialized headers
[MaxLength(2000)]
public string? AdditionalParameters { get; set; } // JSON per parametri aggiuntivi
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
[MaxLength(100)]
public string? CreatedBy { get; set; }
public bool IsActive { get; set; } = true;
}
@@ -0,0 +1,288 @@
namespace CredentialManager.Models;
/// <summary>
/// Tipi di credenziali supportate
/// </summary>
public enum CredentialType
{
Database,
RestApi,
OAuth,
ApiKey,
BasicAuth
}
/// <summary>
/// Tipi di servizi REST specifici
/// </summary>
public enum RestServiceType
{
Generic,
SapB1ServiceLayer,
Salesforce
}
/// <summary>
/// Tipi di database supportati (allineato con DataConnection.Enums.DatabaseType)
/// </summary>
public enum DatabaseType
{
SqlServer,
MySql,
PostgreSql,
Oracle,
Sqlite,
DB2,
SapHana
}
/// <summary>
/// DTO per le credenziali database - completo per tutti i tipi di DB supportati
/// </summary>
public class DatabaseCredential
{
public string Name { get; set; } = string.Empty;
public DatabaseType DatabaseType { get; set; }
public string Host { get; set; } = string.Empty;
public int Port { get; set; }
public string? DatabaseName { get; set; } = string.Empty; // Ora opzionale
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? ConnectionString { get; set; }
public int CommandTimeout { get; set; } = 30;
public bool IgnoreSslErrors { get; set; } = false;
public Dictionary<string, string>? AdditionalParameters { get; set; }
}
/// <summary>
/// DTO per le credenziali REST API - allineato con RestServiceOptions e esteso per servizi specifici
/// </summary>
public class RestApiCredential
{
public string Name { get; set; } = string.Empty;
public RestServiceType ServiceType { get; set; } = RestServiceType.Generic;
public string BaseUrl { get; set; } = string.Empty;
public string? ApiKey { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public string? AuthToken { get; set; }
public string? BearerToken { get; set; }
public int TimeoutSeconds { get; set; } = 100;
public bool IgnoreSslErrors { get; set; } = false;
public Dictionary<string, string>? Headers { get; set; }
public Dictionary<string, string>? AdditionalParameters { get; set; }
// Campi specifici per SAP B1 Service Layer
public string? CompanyDatabase { get; set; }
public string? Language { get; set; } = "en-US";
public string? Version { get; set; } = "v1";
public bool UseTrustedConnection { get; set; } = false;
// Campi specifici per Salesforce
public string? SecurityToken { get; set; }
public string? ClientId { get; set; }
public string? ClientSecret { get; set; }
public string? ApiVersion { get; set; } = "59.0";
public bool IsSandbox { get; set; } = false;
public bool UseSoapApi { get; set; } = false;
public string? RefreshToken { get; set; }
public string? AccessToken { get; set; }
public DateTime? TokenExpiry { get; set; }
}
/// <summary>
/// Credenziali specifiche per SAP Business One Service Layer
/// </summary>
public class SapB1ServiceLayerCredential
{
public string Name { get; set; } = string.Empty;
public string ServerUrl { get; set; } = string.Empty; // es: https://server:50000/b1s/v1/
public string CompanyDatabase { get; set; } = string.Empty; // Database dell'azienda SAP
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string? Language { get; set; } = "en-US"; // Lingua per la sessione
public int TimeoutSeconds { get; set; } = 300; // Timeout per le chiamate API
public bool IgnoreSslErrors { get; set; } = false;
public string? Version { get; set; } = "v1"; // Versione del Service Layer (v1, v2, etc.)
public bool UseTrustedConnection { get; set; } = false; // Per autenticazione Windows
public Dictionary<string, string>? AdditionalHeaders { get; set; }
}
/// <summary>
/// Credenziali specifiche per Salesforce
/// </summary>
public class SalesforceCredential
{
public string Name { get; set; } = string.Empty;
public string LoginUrl { get; set; } = "https://login.salesforce.com"; // o https://test.salesforce.com per sandbox
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string SecurityToken { get; set; } = string.Empty; // Token di sicurezza Salesforce
public string? ClientId { get; set; } = string.Empty; // Consumer Key per Connected App
public string? ClientSecret { get; set; } = string.Empty; // Consumer Secret per Connected App
public string ApiVersion { get; set; } = "59.0"; // Versione API Salesforce
public bool IsSandbox { get; set; } = false; // Se è un ambiente sandbox
public int TimeoutSeconds { get; set; } = 120;
public bool UseSoapApi { get; set; } = false; // Se usare SOAP invece di REST
public string? RefreshToken { get; set; }
public string? AccessToken { get; set; }
public DateTime? TokenExpiry { get; set; }
}
/// <summary>
/// Factory per creare connection string per i diversi tipi di database
/// </summary>
public static class ConnectionStringBuilder
{
public static string BuildConnectionString(DatabaseCredential credential)
{
if (!string.IsNullOrEmpty(credential.ConnectionString))
return credential.ConnectionString;
return credential.DatabaseType switch
{
DatabaseType.SqlServer => BuildSqlServerConnectionString(credential),
DatabaseType.MySql => BuildMySqlConnectionString(credential),
DatabaseType.PostgreSql => BuildPostgreSqlConnectionString(credential),
DatabaseType.Oracle => BuildOracleConnectionString(credential),
DatabaseType.Sqlite => BuildSqliteConnectionString(credential),
DatabaseType.DB2 => BuildDb2ConnectionString(credential),
DatabaseType.SapHana => BuildSapHanaConnectionString(credential),
_ => throw new NotSupportedException($"Database type {credential.DatabaseType} not supported")
};
} private static string BuildSqlServerConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host},{credential.Port}",
$"User Id={credential.Username}",
$"Password={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("TrustServerCertificate=True");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildMySqlConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}",
$"Port={credential.Port}",
$"Uid={credential.Username}",
$"Pwd={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("SslMode=None");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildPostgreSqlConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Host={credential.Host}",
$"Port={credential.Port}",
$"Username={credential.Username}",
$"Password={credential.Password}",
$"Timeout={credential.CommandTimeout}"
};
// Aggiungi Database solo se specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
builder.Add($"Database={credential.DatabaseName}");
if (credential.IgnoreSslErrors)
builder.Add("SSL Mode=Disable");
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
} private static string BuildOracleConnectionString(DatabaseCredential credential)
{
var builder = new List<string>();
// Per Oracle, il formato cambia se c'è o meno un database specificato
if (!string.IsNullOrEmpty(credential.DatabaseName))
{
builder.Add($"Data Source={credential.Host}:{credential.Port}/{credential.DatabaseName}");
}
else
{
builder.Add($"Data Source={credential.Host}:{credential.Port}");
}
builder.AddRange(new[]
{
$"User Id={credential.Username}",
$"Password={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
});
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildSqliteConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Data Source={credential.DatabaseName}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildDb2ConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}:{credential.Port}",
$"Database={credential.DatabaseName}",
$"UID={credential.Username}",
$"PWD={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static string BuildSapHanaConnectionString(DatabaseCredential credential)
{
var builder = new List<string>
{
$"Server={credential.Host}:{credential.Port}",
$"DatabaseName={credential.DatabaseName}",
$"UserID={credential.Username}",
$"Password={credential.Password}",
$"Connection Timeout={credential.CommandTimeout}"
};
AddAdditionalParameters(builder, credential.AdditionalParameters);
return string.Join(";", builder);
}
private static void AddAdditionalParameters(List<string> builder, Dictionary<string, string>? additionalParams)
{
if (additionalParams != null)
{
foreach (var param in additionalParams)
{
builder.Add($"{param.Key}={param.Value}");
}
}
}
}
+348
View File
@@ -0,0 +1,348 @@
# CredentialManager
Un sistema sicuro per la gestione delle credenziali di database e API REST utilizzando Entity Framework Core e SQLite, specificamente progettato per integrarsi con il progetto DataConnection.
## Caratteristiche
- ✅ Memorizzazione sicura delle credenziali con crittografia cross-platform
- ✅ Supporto completo per tutti i database del progetto DataConnection:
- SQL Server, MySQL, PostgreSQL, Oracle, SQLite, DB2, SAP HANA
- ✅ Supporto per credenziali API REST (API Key, Basic Auth, Bearer Token)
- ✅ Generazione automatica di connection string per tutti i tipi di database
- ✅ Database SQLite con Entity Framework Core
- ✅ Creazione automatica del database se non esiste
- ✅ Logging integrato
- ✅ Cross-platform (Windows, Linux, macOS)
- ✅ Dependency Injection pronto
- ✅ Validazione delle credenziali
- ✅ Metodi di utilità per integrazione con DataConnection
## Tipi di Database Supportati
Il CredentialManager supporta tutti i tipi di database gestiti dal progetto DataConnection:
- **SQL Server** (porta predefinita: 1433)
- **MySQL** (porta predefinita: 3306)
- **PostgreSQL** (porta predefinita: 5432)
- **Oracle** (porta predefinita: 1521)
- **SQLite** (database locale)
- **DB2** (porta predefinita: 50000)
- **SAP HANA** (porta predefinita: 30015)
## Installazione
1. Aggiungi il progetto CredentialManager alla tua soluzione
2. Referenzia il progetto nel tuo progetto principale
## Utilizzo Base
### Utilizzo Standalone
```csharp
using CredentialManager;
using CredentialManager.Models;
using CredentialManager.Integration;
// Crea un'istanza del servizio
var credentialService = await CredentialManagerFactory.CreateAsync();
// Salva una credenziale database con validazione
var dbCredential = new DatabaseCredential
{
Name = "Database Produzione",
DatabaseType = DatabaseType.SqlServer,
Host = "sql-server.company.com",
Port = 1433, // Si può omettere, verrà usata la porta predefinita
DatabaseName = "ProductionDB",
Username = "dbuser",
Password = "secretpassword",
CommandTimeout = 30,
IgnoreSslErrors = false,
AdditionalParameters = new Dictionary<string, string>
{
["Encrypt"] = "True",
["TrustServerCertificate"] = "True"
}
};
// Salva con validazione automatica
var id = await credentialService.SaveValidatedDatabaseCredentialAsync(dbCredential);
// Genera automaticamente la connection string
var connectionString = dbCredential.ToConnectionString();
Console.WriteLine($"Connection String: {connectionString}");
```
### Integrazione con DataConnection
```csharp
using CredentialManager.Integration;
// Ottieni credenziale validata
var credential = await credentialService.GetValidatedDatabaseCredentialAsync("Database Produzione");
// Converti per l'uso con DbManagerOptions
var dbOptions = credential.ToDbManagerOptions();
// Oppure ottieni direttamente la connection string
var connectionString = credential.ToConnectionString();
```
### Utilizzo con Dependency Injection
```csharp
using CredentialManager;
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Registra i servizi
services.AddLogging(builder => builder.AddConsole());
services.AddCredentialManager("path/to/credentials.db"); // Percorso opzionale
var serviceProvider = services.BuildServiceProvider();
// Inizializza il database
await serviceProvider.InitializeCredentialManagerAsync();
// Usa il servizio
var credentialService = serviceProvider.GetRequiredService<ICredentialService>();
```
## Tipi di Credenziali Supportate
### Credenziali Database
```csharp
var dbCredential = new DatabaseCredential
{
Name = "Nome Univoco",
DatabaseType = DatabaseType.SqlServer, // Tutti i tipi supportati da DataConnection
Host = "server.company.com",
Port = 1433, // Opzionale, usa porta predefinita se omesso
DatabaseName = "MyDatabase",
Username = "dbuser",
Password = "password", // Verrà crittata automaticamente
ConnectionString = "Server=...", // Opzionale, stringa di connessione completa
CommandTimeout = 30, // Timeout comandi in secondi
IgnoreSslErrors = false, // Per connessioni SSL non verificate
AdditionalParameters = new Dictionary<string, string>
{
["Encrypt"] = "True",
["TrustServerCertificate"] = "True"
}
};
await credentialService.SaveValidatedDatabaseCredentialAsync(dbCredential);
```
### Credenziali REST API
```csharp
var apiCredential = new RestApiCredential
{
Name = "API Esterna",
BaseUrl = "https://api.external-service.com",
ApiKey = "your-api-key", // Verrà crittata automaticamente
Username = "apiuser", // Per Basic Auth
Password = "apipass", // Per Basic Auth, verrà crittata
AuthToken = "bearer-token", // Per Bearer Auth, verrà crittata
TimeoutSeconds = 60, // Timeout richieste
IgnoreSslErrors = false, // Per API con certificati non verificati
Headers = new Dictionary<string, string>
{
["Content-Type"] = "application/json",
["Accept"] = "application/json"
},
AdditionalParameters = new Dictionary<string, string>
{
["CustomParam"] = "value"
}
};
await credentialService.SaveValidatedRestApiCredentialAsync(apiCredential);
```
## Generazione Connection String
Il CredentialManager può generare automaticamente connection string per tutti i tipi di database:
```csharp
// Genera connection string automaticamente
var connectionString = credential.ToConnectionString();
// Connection string specifiche per tipo di database:
// SQL Server: "Server=host,port;Database=dbname;User Id=user;Password=pass;Connection Timeout=30"
// MySQL: "Server=host;Port=port;Database=dbname;Uid=user;Pwd=pass;Connection Timeout=30"
// PostgreSQL: "Host=host;Port=port;Database=dbname;Username=user;Password=pass;Timeout=30"
// Oracle: "Data Source=host:port/dbname;User Id=user;Password=pass;Connection Timeout=30"
// SQLite: "Data Source=dbname"
// DB2: "Server=host:port;Database=dbname;UID=user;PWD=pass;Connection Timeout=30"
// SAP HANA: "Server=host:port;DatabaseName=dbname;UserID=user;Password=pass;Connection Timeout=30"
```
## Validazione delle Credenziali
```csharp
using CredentialManager.Integration;
// Validazione manuale
var errors = DataConnectionHelper.ValidateDatabaseCredential(credential);
if (errors.Any())
{
Console.WriteLine($"Errori: {string.Join(", ", errors)}");
}
// Validazione automatica durante il salvataggio
try
{
await credentialService.SaveValidatedDatabaseCredentialAsync(credential);
}
catch (ArgumentException ex)
{
Console.WriteLine($"Credenziale non valida: {ex.Message}");
}
```
## Operazioni Principali
### Salvare Credenziali
```csharp
// Con validazione automatica (raccomandato)
var dbId = await credentialService.SaveValidatedDatabaseCredentialAsync(databaseCredential);
var apiId = await credentialService.SaveValidatedRestApiCredentialAsync(apiCredential);
// Senza validazione
var dbId = await credentialService.SaveDatabaseCredentialAsync(databaseCredential);
var apiId = await credentialService.SaveRestApiCredentialAsync(apiCredential);
```
### Recuperare Credenziali
```csharp
// Con validazione automatica (raccomandato)
var dbCred = await credentialService.GetValidatedDatabaseCredentialAsync("Database Produzione");
var apiCred = await credentialService.GetValidatedRestApiCredentialAsync("API Esterna");
// Senza validazione
var dbCred = await credentialService.GetDatabaseCredentialAsync("Database Produzione");
var apiCred = await credentialService.GetRestApiCredentialAsync("API Esterna");
// Per ID
var dbCred = await credentialService.GetDatabaseCredentialAsync(1);
var apiCred = await credentialService.GetRestApiCredentialAsync(2);
// Tutte le credenziali di un tipo
var allDbCreds = await credentialService.GetAllDatabaseCredentialsAsync();
var allApiCreds = await credentialService.GetAllRestApiCredentialsAsync();
```
### Eliminare Credenziali
```csharp
// Per nome
await credentialService.DeleteCredentialAsync("Nome Credenziale");
// Per ID
await credentialService.DeleteCredentialAsync(1);
```
### Ottenere Lista Nomi
```csharp
// Tutti i nomi
var allNames = await credentialService.GetCredentialNamesAsync();
// Solo nomi di un tipo specifico
var dbNames = await credentialService.GetCredentialNamesAsync(CredentialType.Database);
var apiNames = await credentialService.GetCredentialNamesAsync(CredentialType.RestApi);
```
## Configurazione
### Percorso Database Personalizzato
```csharp
// Specificare un percorso personalizzato per il database
services.AddCredentialManager("/custom/path/credentials.db");
// O per utilizzo standalone
var service = await CredentialManagerFactory.CreateAsync("/custom/path/credentials.db");
```
### Logging Personalizzato
```csharp
var loggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole().SetMinimumLevel(LogLevel.Debug));
var service = await CredentialManagerFactory.CreateAsync(
databasePath: "credentials.db",
loggerFactory: loggerFactory);
```
## Sicurezza
- Le password, API key e token sono crittati utilizzando:
- **Windows**: `ProtectedData` con entropia personalizzata
- **Altri OS**: AES-256 con chiave derivata
- I dati sono memorizzati localmente in un database SQLite
- Le credenziali eliminate sono contrassegnate come inattive (soft delete)
- Validazione automatica dei dati di input
## Struttura Database
Il database SQLite viene creato automaticamente con la seguente struttura:
```sql
CREATE TABLE Credentials (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Name NVARCHAR(100) NOT NULL UNIQUE,
Type NVARCHAR(50) NOT NULL,
DatabaseType NVARCHAR(50),
Host NVARCHAR(200),
Port INTEGER,
DatabaseName NVARCHAR(100),
Username NVARCHAR(100),
EncryptedPassword TEXT,
ConnectionString NVARCHAR(500),
EncryptedApiKey NVARCHAR(500),
EncryptedAuthToken NVARCHAR(500),
CommandTimeout INTEGER DEFAULT 30,
TimeoutSeconds INTEGER DEFAULT 100,
IgnoreSslErrors INTEGER DEFAULT 0,
Headers NVARCHAR(2000),
AdditionalParameters NVARCHAR(2000),
CreatedAt DATETIME NOT NULL,
UpdatedAt DATETIME,
CreatedBy NVARCHAR(100),
IsActive INTEGER NOT NULL DEFAULT 1
);
```
## Porte Predefinite
Il sistema assegna automaticamente le porte predefinite se non specificate:
- SQL Server: 1433
- MySQL: 3306
- PostgreSQL: 5432
- Oracle: 1521
- DB2: 50000
- SAP HANA: 30015
- SQLite: N/A (database locale)
## Requisiti
- .NET 9.0
- Entity Framework Core 9.0
- SQLite
## Note
- Il database viene creato nella cartella `%APPDATA%/CredentialManager/` se non specificato un percorso personalizzato
- Tutte le operazioni sono asincrone
- Il servizio è thread-safe
- Le migrazioni del database vengono applicate automaticamente all'avvio
- Piena compatibilità con il progetto DataConnection
@@ -0,0 +1,922 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CredentialManager.Data;
using CredentialManager.Models;
using System.Text.Json;
namespace CredentialManager.Services;
/// <summary>
/// Interfaccia per il servizio di gestione credenziali
/// </summary>
public interface ICredentialService
{
// Database Credentials
Task<int> SaveDatabaseCredentialAsync(DatabaseCredential credential);
Task<DatabaseCredential?> GetDatabaseCredentialAsync(string name);
Task<DatabaseCredential?> GetDatabaseCredentialAsync(int id);
Task<List<DatabaseCredential>> GetAllDatabaseCredentialsAsync();
// REST API Credentials
Task<int> SaveRestApiCredentialAsync(RestApiCredential credential);
Task<RestApiCredential?> GetRestApiCredentialAsync(string name);
Task<RestApiCredential?> GetRestApiCredentialAsync(int id);
Task<List<RestApiCredential>> GetAllRestApiCredentialsAsync();
// SAP B1 Service Layer Credentials
Task<int> SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential);
Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(string name);
Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(int id);
Task<List<SapB1ServiceLayerCredential>> GetAllSapB1CredentialsAsync();
// Salesforce Credentials
Task<int> SaveSalesforceCredentialAsync(SalesforceCredential credential);
Task<SalesforceCredential?> GetSalesforceCredentialAsync(string name);
Task<SalesforceCredential?> GetSalesforceCredentialAsync(int id);
Task<List<SalesforceCredential>> GetAllSalesforceCredentialsAsync();
// Generic operations
Task<bool> DeleteCredentialAsync(int id);
Task<bool> DeleteCredentialAsync(string name);
Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null);
}
/// <summary>
/// Servizio per la gestione delle credenziali
/// </summary>
public class CredentialService : ICredentialService
{
private readonly CredentialDbContext _context;
private readonly IEncryptionService _encryptionService;
private readonly ILogger<CredentialService> _logger;
public CredentialService(
CredentialDbContext context,
IEncryptionService encryptionService,
ILogger<CredentialService> logger)
{
_context = context;
_encryptionService = encryptionService;
_logger = logger;
}
#region Database Credentials
public async Task<int> SaveDatabaseCredentialAsync(DatabaseCredential credential)
{
try
{
var entity = new CredentialEntity
{
Name = credential.Name,
Type = CredentialType.Database.ToString(),
DatabaseType = credential.DatabaseType.ToString(),
Host = credential.Host,
Port = credential.Port,
DatabaseName = credential.DatabaseName,
Username = credential.Username,
EncryptedPassword = _encryptionService.Encrypt(credential.Password),
ConnectionString = credential.ConnectionString,
CommandTimeout = credential.CommandTimeout,
IgnoreSslErrors = credential.IgnoreSslErrors,
AdditionalParameters = credential.AdditionalParameters != null
? JsonSerializer.Serialize(credential.AdditionalParameters)
: null,
CreatedAt = DateTime.UtcNow,
CreatedBy = Environment.UserName
};
// Verifica se esiste già una credenziale con lo stesso nome
var existing = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == credential.Name);
if (existing != null)
{
// Aggiorna esistente
existing.DatabaseType = entity.DatabaseType;
existing.Host = entity.Host;
existing.Port = entity.Port;
existing.DatabaseName = entity.DatabaseName;
existing.Username = entity.Username;
existing.EncryptedPassword = entity.EncryptedPassword;
existing.ConnectionString = entity.ConnectionString;
existing.CommandTimeout = entity.CommandTimeout;
existing.IgnoreSslErrors = entity.IgnoreSslErrors;
existing.AdditionalParameters = entity.AdditionalParameters;
existing.UpdatedAt = DateTime.UtcNow;
_context.Credentials.Update(existing);
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale database aggiornata: {Name}", credential.Name);
return existing.Id;
}
else
{
// Crea nuovo
_context.Credentials.Add(entity);
await _context.SaveChangesAsync();
_logger.LogInformation("Nuova credenziale database salvata: {Name}", credential.Name);
return entity.Id;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel salvare la credenziale database: {Name}", credential.Name);
throw;
}
}
public async Task<DatabaseCredential?> GetDatabaseCredentialAsync(string name)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name && c.Type == CredentialType.Database.ToString() && c.IsActive);
return entity != null ? MapToDatabaseCredential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale database: {Name}", name);
throw;
}
}
public async Task<DatabaseCredential?> GetDatabaseCredentialAsync(int id)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Id == id && c.Type == CredentialType.Database.ToString() && c.IsActive);
return entity != null ? MapToDatabaseCredential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale database: {Id}", id);
throw;
}
}
public async Task<List<DatabaseCredential>> GetAllDatabaseCredentialsAsync()
{
try
{
var entities = await _context.Credentials
.Where(c => c.Type == CredentialType.Database.ToString() && c.IsActive)
.ToListAsync();
return entities.Select(MapToDatabaseCredential).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare tutte le credenziali database");
throw;
}
}
#endregion
#region REST API Credentials
public async Task<int> SaveRestApiCredentialAsync(RestApiCredential credential)
{
try
{
// Prepara i parametri aggiuntivi per includere i campi specifici del servizio
var additionalParams = new Dictionary<string, string>();
// Copia i parametri aggiuntivi esistenti
if (credential.AdditionalParameters != null)
{
foreach (var param in credential.AdditionalParameters)
{
additionalParams[param.Key] = param.Value;
}
}
// Aggiungi campi specifici per SAP B1
if (credential.ServiceType == RestServiceType.SapB1ServiceLayer)
{
if (!string.IsNullOrEmpty(credential.CompanyDatabase))
additionalParams["CompanyDatabase"] = credential.CompanyDatabase;
if (!string.IsNullOrEmpty(credential.Language))
additionalParams["Language"] = credential.Language;
if (!string.IsNullOrEmpty(credential.Version))
additionalParams["Version"] = credential.Version;
additionalParams["UseTrustedConnection"] = credential.UseTrustedConnection.ToString();
}
// Aggiungi campi specifici per Salesforce
if (credential.ServiceType == RestServiceType.Salesforce)
{
if (!string.IsNullOrEmpty(credential.SecurityToken))
additionalParams["SecurityToken"] = credential.SecurityToken;
if (!string.IsNullOrEmpty(credential.ClientId))
additionalParams["ClientId"] = credential.ClientId;
if (!string.IsNullOrEmpty(credential.ClientSecret))
additionalParams["ClientSecret"] = credential.ClientSecret;
if (!string.IsNullOrEmpty(credential.ApiVersion))
additionalParams["ApiVersion"] = credential.ApiVersion;
additionalParams["IsSandbox"] = credential.IsSandbox.ToString();
additionalParams["UseSoapApi"] = credential.UseSoapApi.ToString();
if (!string.IsNullOrEmpty(credential.RefreshToken))
additionalParams["RefreshToken"] = credential.RefreshToken;
if (!string.IsNullOrEmpty(credential.AccessToken))
additionalParams["AccessToken"] = credential.AccessToken;
if (credential.TokenExpiry.HasValue)
additionalParams["TokenExpiry"] = credential.TokenExpiry.Value.ToString("O");
}
var entity = new CredentialEntity
{
Name = credential.Name,
Type = CredentialType.RestApi.ToString(),
RestServiceType = credential.ServiceType.ToString(),
Host = credential.BaseUrl,
Username = credential.Username,
EncryptedPassword = !string.IsNullOrEmpty(credential.Password)
? _encryptionService.Encrypt(credential.Password)
: null,
EncryptedApiKey = !string.IsNullOrEmpty(credential.ApiKey)
? _encryptionService.Encrypt(credential.ApiKey)
: null,
EncryptedAuthToken = !string.IsNullOrEmpty(credential.AuthToken)
? _encryptionService.Encrypt(credential.AuthToken)
: null,
TimeoutSeconds = credential.TimeoutSeconds,
IgnoreSslErrors = credential.IgnoreSslErrors,
Headers = credential.Headers != null
? JsonSerializer.Serialize(credential.Headers)
: null,
AdditionalParameters = additionalParams.Count > 0
? JsonSerializer.Serialize(additionalParams)
: null,
CreatedAt = DateTime.UtcNow,
CreatedBy = Environment.UserName
};
// Verifica se esiste già una credenziale con lo stesso nome
var existing = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == credential.Name);
if (existing != null)
{
// Aggiorna esistente
existing.RestServiceType = entity.RestServiceType;
existing.Host = entity.Host;
existing.Username = entity.Username;
existing.EncryptedPassword = entity.EncryptedPassword;
existing.EncryptedApiKey = entity.EncryptedApiKey;
existing.EncryptedAuthToken = entity.EncryptedAuthToken;
existing.TimeoutSeconds = entity.TimeoutSeconds;
existing.IgnoreSslErrors = entity.IgnoreSslErrors;
existing.Headers = entity.Headers;
existing.AdditionalParameters = entity.AdditionalParameters;
existing.UpdatedAt = DateTime.UtcNow;
_context.Credentials.Update(existing);
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale REST API aggiornata: {Name}", credential.Name);
return existing.Id;
}
else
{
// Crea nuovo
_context.Credentials.Add(entity);
await _context.SaveChangesAsync();
_logger.LogInformation("Nuova credenziale REST API salvata: {Name}", credential.Name);
return entity.Id;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel salvare la credenziale REST API: {Name}", credential.Name);
throw;
}
}
public async Task<RestApiCredential?> GetRestApiCredentialAsync(string name)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name && c.Type == CredentialType.RestApi.ToString() && c.IsActive);
return entity != null ? MapToRestApiCredential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale REST API: {Name}", name);
throw;
}
}
public async Task<RestApiCredential?> GetRestApiCredentialAsync(int id)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Id == id && c.Type == CredentialType.RestApi.ToString() && c.IsActive);
return entity != null ? MapToRestApiCredential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale REST API: {Id}", id);
throw;
}
}
public async Task<List<RestApiCredential>> GetAllRestApiCredentialsAsync()
{
try
{
var entities = await _context.Credentials
.Where(c => c.Type == CredentialType.RestApi.ToString() && c.IsActive)
.ToListAsync();
return entities.Select(MapToRestApiCredential).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare tutte le credenziali REST API");
throw;
}
}
#endregion
#region SAP B1 Service Layer Credentials
public async Task<int> SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential)
{
try
{
// Controlla se esiste già una credenziale con lo stesso nome
var existingEntity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == credential.Name && c.Type == CredentialType.RestApi.ToString());
CredentialEntity entity;
if (existingEntity != null)
{
entity = existingEntity;
_logger.LogInformation("Aggiornamento credenziale SAP B1 esistente: {Name}", credential.Name);
}
else
{
entity = new CredentialEntity();
_context.Credentials.Add(entity);
_logger.LogInformation("Creazione nuova credenziale SAP B1: {Name}", credential.Name);
}
// Popola i campi dell'entità
entity.Name = credential.Name;
entity.Type = CredentialType.RestApi.ToString();
entity.RestServiceType = RestServiceType.SapB1ServiceLayer.ToString();
entity.Host = credential.ServerUrl;
entity.Username = credential.Username;
entity.EncryptedPassword = _encryptionService.Encrypt(credential.Password);
entity.TimeoutSeconds = credential.TimeoutSeconds;
entity.IgnoreSslErrors = credential.IgnoreSslErrors;
entity.Headers = credential.AdditionalHeaders != null
? JsonSerializer.Serialize(credential.AdditionalHeaders)
: null;
// Salva parametri aggiuntivi specifici per SAP B1
var additionalParams = new Dictionary<string, string>
{
["CompanyDatabase"] = credential.CompanyDatabase,
["Language"] = credential.Language ?? "en-US",
["Version"] = credential.Version ?? "v1",
["UseTrustedConnection"] = credential.UseTrustedConnection.ToString()
};
entity.AdditionalParameters = JsonSerializer.Serialize(additionalParams);
entity.CreatedAt = existingEntity?.CreatedAt ?? DateTime.UtcNow;
entity.CreatedBy = existingEntity?.CreatedBy ?? Environment.UserName;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale SAP B1 salvata con ID: {Id}", entity.Id);
return entity.Id;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel salvare la credenziale SAP B1: {Name}", credential.Name);
throw;
}
}
public async Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(string name)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name &&
c.Type == CredentialType.RestApi.ToString() &&
c.RestServiceType == RestServiceType.SapB1ServiceLayer.ToString() &&
c.IsActive);
return entity != null ? MapToSapB1Credential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale SAP B1: {Name}", name);
throw;
}
}
public async Task<SapB1ServiceLayerCredential?> GetSapB1CredentialAsync(int id)
{
try
{
var entity = await _context.Credentials.FindAsync(id);
if (entity == null ||
entity.Type != CredentialType.RestApi.ToString() ||
entity.RestServiceType != RestServiceType.SapB1ServiceLayer.ToString() ||
!entity.IsActive)
return null;
return MapToSapB1Credential(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale SAP B1 con ID: {Id}", id);
throw;
}
}
public async Task<List<SapB1ServiceLayerCredential>> GetAllSapB1CredentialsAsync()
{
try
{
var entities = await _context.Credentials
.Where(c => c.Type == CredentialType.RestApi.ToString() &&
c.RestServiceType == RestServiceType.SapB1ServiceLayer.ToString() &&
c.IsActive)
.ToListAsync();
return entities.Select(MapToSapB1Credential).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare tutte le credenziali SAP B1");
throw;
}
}
#endregion
#region Salesforce Credentials
public async Task<int> SaveSalesforceCredentialAsync(SalesforceCredential credential)
{
try
{
// Controlla se esiste già una credenziale con lo stesso nome
var existingEntity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == credential.Name && c.Type == CredentialType.RestApi.ToString());
CredentialEntity entity;
if (existingEntity != null)
{
entity = existingEntity;
_logger.LogInformation("Aggiornamento credenziale Salesforce esistente: {Name}", credential.Name);
}
else
{
entity = new CredentialEntity();
_context.Credentials.Add(entity);
_logger.LogInformation("Creazione nuova credenziale Salesforce: {Name}", credential.Name);
}
// Popola i campi dell'entità
entity.Name = credential.Name;
entity.Type = CredentialType.RestApi.ToString();
entity.RestServiceType = RestServiceType.Salesforce.ToString();
entity.Host = credential.LoginUrl;
entity.Username = credential.Username;
entity.EncryptedPassword = _encryptionService.Encrypt(credential.Password);
entity.TimeoutSeconds = credential.TimeoutSeconds;
// Salva parametri aggiuntivi specifici per Salesforce
var additionalParams = new Dictionary<string, string>
{
["SecurityToken"] = credential.SecurityToken,
["ApiVersion"] = credential.ApiVersion,
["IsSandbox"] = credential.IsSandbox.ToString(),
["UseSoapApi"] = credential.UseSoapApi.ToString()
};
// Aggiungi ClientId e ClientSecret se forniti
if (!string.IsNullOrEmpty(credential.ClientId))
additionalParams["ClientId"] = credential.ClientId;
if (!string.IsNullOrEmpty(credential.ClientSecret))
additionalParams["ClientSecret"] = credential.ClientSecret;
if (!string.IsNullOrEmpty(credential.RefreshToken))
additionalParams["RefreshToken"] = credential.RefreshToken;
if (!string.IsNullOrEmpty(credential.AccessToken))
additionalParams["AccessToken"] = credential.AccessToken;
if (credential.TokenExpiry.HasValue)
additionalParams["TokenExpiry"] = credential.TokenExpiry.Value.ToString("O");
entity.AdditionalParameters = JsonSerializer.Serialize(additionalParams);
entity.CreatedAt = existingEntity?.CreatedAt ?? DateTime.UtcNow;
entity.CreatedBy = existingEntity?.CreatedBy ?? Environment.UserName;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale Salesforce salvata con ID: {Id}", entity.Id);
return entity.Id;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel salvare la credenziale Salesforce: {Name}", credential.Name);
throw;
}
}
public async Task<SalesforceCredential?> GetSalesforceCredentialAsync(string name)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name &&
c.Type == CredentialType.RestApi.ToString() &&
c.RestServiceType == RestServiceType.Salesforce.ToString() &&
c.IsActive);
return entity != null ? MapToSalesforceCredential(entity) : null;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale Salesforce: {Name}", name);
throw;
}
}
public async Task<SalesforceCredential?> GetSalesforceCredentialAsync(int id)
{
try
{
var entity = await _context.Credentials.FindAsync(id);
if (entity == null ||
entity.Type != CredentialType.RestApi.ToString() ||
entity.RestServiceType != RestServiceType.Salesforce.ToString() ||
!entity.IsActive)
return null;
return MapToSalesforceCredential(entity);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare la credenziale Salesforce con ID: {Id}", id);
throw;
}
}
public async Task<List<SalesforceCredential>> GetAllSalesforceCredentialsAsync()
{
try
{
var entities = await _context.Credentials
.Where(c => c.Type == CredentialType.RestApi.ToString() &&
c.RestServiceType == RestServiceType.Salesforce.ToString() &&
c.IsActive)
.ToListAsync();
return entities.Select(MapToSalesforceCredential).ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare tutte le credenziali Salesforce");
throw;
}
}
#endregion
#region Generic Operations
public async Task<bool> DeleteCredentialAsync(int id)
{
try
{
var entity = await _context.Credentials.FindAsync(id);
if (entity == null) return false;
entity.IsActive = false;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale eliminata (soft delete): {Name} (ID: {Id})", entity.Name, entity.Id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'eliminare la credenziale con ID: {Id}", id);
throw;
}
}
public async Task<bool> DeleteCredentialAsync(string name)
{
try
{
var entity = await _context.Credentials
.FirstOrDefaultAsync(c => c.Name == name && c.IsActive);
if (entity == null) return false;
entity.IsActive = false;
entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
_logger.LogInformation("Credenziale eliminata (soft delete): {Name} (ID: {Id})", entity.Name, entity.Id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'eliminare la credenziale: {Name}", name);
throw;
}
}
public async Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null)
{
try
{
var query = _context.Credentials.Where(c => c.IsActive);
if (type.HasValue)
query = query.Where(c => c.Type == type.ToString());
return await query.Select(c => c.Name).ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recuperare i nomi delle credenziali");
throw;
}
}
#endregion
#region Private Mapping Methods
private DatabaseCredential MapToDatabaseCredential(CredentialEntity entity)
{ var credential = new DatabaseCredential
{
Name = entity.Name,
DatabaseType = Enum.Parse<DatabaseType>(entity.DatabaseType!),
Host = entity.Host ?? string.Empty,
Port = entity.Port ?? 0,
DatabaseName = entity.DatabaseName ?? string.Empty,
Username = entity.Username ?? string.Empty,
Password = _encryptionService.Decrypt(entity.EncryptedPassword!),
ConnectionString = entity.ConnectionString,
CommandTimeout = entity.CommandTimeout,
IgnoreSslErrors = entity.IgnoreSslErrors
};
if (!string.IsNullOrEmpty(entity.AdditionalParameters))
{
try
{
credential.AdditionalParameters = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.AdditionalParameters);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare i parametri aggiuntivi per {Name}", entity.Name);
}
}
return credential;
}
private RestApiCredential MapToRestApiCredential(CredentialEntity entity)
{
var credential = new RestApiCredential
{
Name = entity.Name,
ServiceType = Enum.TryParse<RestServiceType>(entity.RestServiceType, out var serviceType)
? serviceType
: RestServiceType.Generic,
BaseUrl = entity.Host ?? string.Empty,
Username = entity.Username,
Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
? _encryptionService.Decrypt(entity.EncryptedPassword)
: null,
ApiKey = !string.IsNullOrEmpty(entity.EncryptedApiKey)
? _encryptionService.Decrypt(entity.EncryptedApiKey)
: null,
AuthToken = !string.IsNullOrEmpty(entity.EncryptedAuthToken)
? _encryptionService.Decrypt(entity.EncryptedAuthToken)
: null,
TimeoutSeconds = entity.TimeoutSeconds,
IgnoreSslErrors = entity.IgnoreSslErrors
};
if (!string.IsNullOrEmpty(entity.Headers))
{
try
{
credential.Headers = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.Headers);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare gli headers per {Name}", entity.Name);
}
}
if (!string.IsNullOrEmpty(entity.AdditionalParameters))
{
try
{
var additionalParams = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.AdditionalParameters);
if (additionalParams != null)
{
// Imposta i parametri aggiuntivi generici
credential.AdditionalParameters = new Dictionary<string, string>();
// Mappa i campi specifici per SAP B1
if (credential.ServiceType == RestServiceType.SapB1ServiceLayer)
{
if (additionalParams.TryGetValue("CompanyDatabase", out var companyDb))
credential.CompanyDatabase = companyDb;
if (additionalParams.TryGetValue("Language", out var language))
credential.Language = language;
if (additionalParams.TryGetValue("Version", out var version))
credential.Version = version;
if (additionalParams.TryGetValue("UseTrustedConnection", out var useTrusted) && bool.TryParse(useTrusted, out var trusted))
credential.UseTrustedConnection = trusted;
}
// Mappa i campi specifici per Salesforce
else if (credential.ServiceType == RestServiceType.Salesforce)
{
if (additionalParams.TryGetValue("SecurityToken", out var securityToken))
credential.SecurityToken = securityToken;
if (additionalParams.TryGetValue("ClientId", out var clientId))
credential.ClientId = clientId;
if (additionalParams.TryGetValue("ClientSecret", out var clientSecret))
credential.ClientSecret = clientSecret;
if (additionalParams.TryGetValue("ApiVersion", out var apiVersion))
credential.ApiVersion = apiVersion;
if (additionalParams.TryGetValue("IsSandbox", out var isSandbox) && bool.TryParse(isSandbox, out var sandbox))
credential.IsSandbox = sandbox;
if (additionalParams.TryGetValue("UseSoapApi", out var useSoap) && bool.TryParse(useSoap, out var soap))
credential.UseSoapApi = soap;
if (additionalParams.TryGetValue("RefreshToken", out var refreshToken))
credential.RefreshToken = refreshToken;
if (additionalParams.TryGetValue("AccessToken", out var accessToken))
credential.AccessToken = accessToken;
if (additionalParams.TryGetValue("TokenExpiry", out var tokenExpiry) && DateTime.TryParse(tokenExpiry, out var expiry))
credential.TokenExpiry = expiry;
}
// Copia tutti i parametri che non sono specifici del servizio
var serviceSpecificKeys = new HashSet<string>
{
"CompanyDatabase", "Language", "Version", "UseTrustedConnection",
"SecurityToken", "ClientId", "ClientSecret", "ApiVersion",
"IsSandbox", "UseSoapApi", "RefreshToken", "AccessToken", "TokenExpiry"
};
foreach (var param in additionalParams)
{
if (!serviceSpecificKeys.Contains(param.Key))
{
credential.AdditionalParameters[param.Key] = param.Value;
}
}
// Se non ci sono parametri aggiuntivi generici, imposta a null
if (credential.AdditionalParameters.Count == 0)
credential.AdditionalParameters = null;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare i parametri aggiuntivi per {Name}", entity.Name);
}
}
return credential;
}
private SapB1ServiceLayerCredential MapToSapB1Credential(CredentialEntity entity)
{
var credential = new SapB1ServiceLayerCredential
{
Name = entity.Name,
ServerUrl = entity.Host ?? string.Empty,
Username = entity.Username ?? string.Empty,
Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
? _encryptionService.Decrypt(entity.EncryptedPassword)
: string.Empty,
TimeoutSeconds = entity.TimeoutSeconds,
IgnoreSslErrors = entity.IgnoreSslErrors
};
if (!string.IsNullOrEmpty(entity.Headers))
{
try
{
credential.AdditionalHeaders = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.Headers);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare gli headers per SAP B1 {Name}", entity.Name);
}
}
if (!string.IsNullOrEmpty(entity.AdditionalParameters))
{
try
{
var additionalParams = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.AdditionalParameters);
if (additionalParams != null)
{
if (additionalParams.TryGetValue("CompanyDatabase", out var companyDb))
credential.CompanyDatabase = companyDb;
if (additionalParams.TryGetValue("Language", out var language))
credential.Language = language;
if (additionalParams.TryGetValue("Version", out var version))
credential.Version = version;
if (additionalParams.TryGetValue("UseTrustedConnection", out var useTrusted) && bool.TryParse(useTrusted, out var trusted))
credential.UseTrustedConnection = trusted;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare i parametri aggiuntivi per SAP B1 {Name}", entity.Name);
}
}
return credential;
}
private SalesforceCredential MapToSalesforceCredential(CredentialEntity entity)
{
var credential = new SalesforceCredential
{
Name = entity.Name,
LoginUrl = entity.Host ?? "https://login.salesforce.com",
Username = entity.Username ?? string.Empty,
Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
? _encryptionService.Decrypt(entity.EncryptedPassword)
: string.Empty,
TimeoutSeconds = entity.TimeoutSeconds
};
if (!string.IsNullOrEmpty(entity.AdditionalParameters))
{
try
{
var additionalParams = JsonSerializer.Deserialize<Dictionary<string, string>>(entity.AdditionalParameters);
if (additionalParams != null)
{
if (additionalParams.TryGetValue("SecurityToken", out var securityToken))
credential.SecurityToken = securityToken;
if (additionalParams.TryGetValue("ClientId", out var clientId))
credential.ClientId = clientId;
if (additionalParams.TryGetValue("ClientSecret", out var clientSecret))
credential.ClientSecret = clientSecret;
if (additionalParams.TryGetValue("ApiVersion", out var apiVersion))
credential.ApiVersion = apiVersion;
if (additionalParams.TryGetValue("IsSandbox", out var isSandbox) && bool.TryParse(isSandbox, out var sandbox))
credential.IsSandbox = sandbox;
if (additionalParams.TryGetValue("UseSoapApi", out var useSoap) && bool.TryParse(useSoap, out var soap))
credential.UseSoapApi = soap;
if (additionalParams.TryGetValue("RefreshToken", out var refreshToken))
credential.RefreshToken = refreshToken;
if (additionalParams.TryGetValue("AccessToken", out var accessToken))
credential.AccessToken = accessToken;
if (additionalParams.TryGetValue("TokenExpiry", out var tokenExpiry) && DateTime.TryParse(tokenExpiry, out var expiry))
credential.TokenExpiry = expiry;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Errore nel deserializzare i parametri aggiuntivi per Salesforce {Name}", entity.Name);
}
}
return credential;
}
#endregion
}
@@ -0,0 +1,167 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using CredentialManager.Data;
namespace CredentialManager.Services;
/// <summary>
/// Interfaccia per l'inizializzazione del database
/// </summary>
public interface IDatabaseInitializer
{
Task InitializeAsync();
Task<bool> DatabaseExistsAsync();
}
/// <summary>
/// Servizio per l'inizializzazione del database SQLite
/// </summary>
public class DatabaseInitializer : IDatabaseInitializer
{
private readonly CredentialDbContext _context;
private readonly ILogger<DatabaseInitializer> _logger;
public DatabaseInitializer(CredentialDbContext context, ILogger<DatabaseInitializer> logger)
{
_context = context;
_logger = logger;
} public async Task InitializeAsync()
{
try
{
_logger.LogInformation("Inizializzazione database in corso...");
// Per SQLite con EnsureCreatedAsync è più semplice e sicuro
// Crea il database e le tabelle se non esistono
var created = await _context.Database.EnsureCreatedAsync();
if (created)
{
_logger.LogInformation("Database creato con successo");
// Aggiungi dati di esempio se necessario
await SeedInitialDataAsync();
}
else
{
_logger.LogInformation("Database esistente trovato");
// Verifica che le tabelle esistano
await VerifyTablesAsync();
// Applica migrazioni se necessario
await ApplyMigrationsAsync();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante l'inizializzazione del database");
throw;
}
}
private async Task VerifyTablesAsync()
{
try
{
// Prova a fare una query semplice per verificare che la tabella esista
await _context.Credentials.CountAsync();
_logger.LogInformation("Verifica tabelle completata con successo");
}
catch (Exception ex)
{
_logger.LogWarning("Tabelle mancanti, ricreazione database...");
// Se le tabelle non esistono, le ricreiamo
await _context.Database.EnsureDeletedAsync();
await _context.Database.EnsureCreatedAsync();
await SeedInitialDataAsync();
_logger.LogInformation("Database ricreato con successo");
}
}
public async Task<bool> DatabaseExistsAsync()
{
try
{
return await _context.Database.CanConnectAsync();
}
catch
{
return false;
}
}
private async Task SeedInitialDataAsync()
{
try
{
// Verifica se ci sono già dati
if (await _context.Credentials.AnyAsync())
{
_logger.LogInformation("Dati esistenti trovati. Seeding saltato.");
return;
}
_logger.LogInformation("Aggiunta dati di esempio...");
// Qui puoi aggiungere dati di esempio se necessario
// Ad esempio:
/*
var sampleCredentials = new List<CredentialEntity>
{
new CredentialEntity
{
Name = "Esempio Database",
Type = "Database",
Host = "localhost",
Port = 1433,
DatabaseName = "TestDB",
Username = "testuser",
CreatedAt = DateTime.UtcNow,
CreatedBy = "System",
IsActive = true
}
};
_context.Credentials.AddRange(sampleCredentials);
await _context.SaveChangesAsync();
*/
_logger.LogInformation("Dati di esempio aggiunti con successo");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante il seeding dei dati iniziali");
throw;
}
} private async Task ApplyMigrationsAsync()
{
try
{
_logger.LogInformation("Verifica e applicazione migrazioni...");
// Verifica se la colonna RestServiceType esiste usando una query diretta
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT RestServiceType FROM Credentials LIMIT 1");
_logger.LogInformation("Colonna RestServiceType già presente");
}
catch (Microsoft.Data.Sqlite.SqliteException)
{
// La colonna non esiste, la aggiungiamo
_logger.LogInformation("Aggiunta colonna RestServiceType...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE Credentials ADD COLUMN RestServiceType TEXT");
_logger.LogInformation("Colonna RestServiceType aggiunta con successo");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'applicazione delle migrazioni");
throw;
}
}
}
@@ -0,0 +1,139 @@
using System.Security.Cryptography;
using System.Text;
using System.Runtime.InteropServices;
namespace CredentialManager.Services;
/// <summary>
/// Interfaccia per il servizio di crittografia
/// </summary>
public interface IEncryptionService
{
string Encrypt(string plainText);
string Decrypt(string encryptedText);
}
/// <summary>
/// Servizio per la crittografia delle password cross-platform
/// </summary>
public class EncryptionService : IEncryptionService
{
private readonly byte[] _key;
private readonly byte[] _iv;
public EncryptionService()
{
// Chiave e IV derivati da una stringa fissa (in produzione dovrebbero essere configurabili)
var keySource = "CredentialManager2025KeyForEncryption!";
var ivSource = "CredMgr2025IV!";
using var sha256 = SHA256.Create();
_key = sha256.ComputeHash(Encoding.UTF8.GetBytes(keySource));
_iv = new byte[16];
Array.Copy(Encoding.UTF8.GetBytes(ivSource), _iv, Math.Min(16, ivSource.Length));
}
public string Encrypt(string plainText)
{
if (string.IsNullOrEmpty(plainText))
return string.Empty;
try
{
// Su Windows, usa ProtectedData se disponibile
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return EncryptWithProtectedData(plainText);
}
// Su altre piattaforme, usa AES
return EncryptWithAes(plainText);
}
catch (Exception ex)
{
throw new InvalidOperationException("Errore durante la crittografia", ex);
}
}
public string Decrypt(string encryptedText)
{
if (string.IsNullOrEmpty(encryptedText))
return string.Empty;
try
{
// Su Windows, usa ProtectedData se disponibile
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return DecryptWithProtectedData(encryptedText);
}
// Su altre piattaforme, usa AES
return DecryptWithAes(encryptedText);
}
catch (Exception ex)
{
throw new InvalidOperationException("Errore durante la decrittografia", ex);
}
} private string EncryptWithProtectedData(string plainText)
{
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
if (OperatingSystem.IsWindows())
{
byte[] encryptedBytes = ProtectedData.Protect(plainTextBytes, entropy, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(encryptedBytes);
}
// Fallback ad AES se non su Windows
return EncryptWithAes(plainText);
}
private string DecryptWithProtectedData(string encryptedText)
{
if (OperatingSystem.IsWindows())
{
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
byte[] decryptedBytes = ProtectedData.Unprotect(encryptedBytes, entropy, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(decryptedBytes);
}
// Fallback ad AES se non su Windows
return DecryptWithAes(encryptedText);
}
private string EncryptWithAes(string plainText)
{
using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
using var encryptor = aes.CreateEncryptor();
using var msEncrypt = new MemoryStream();
using var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write);
using var swEncrypt = new StreamWriter(csEncrypt);
swEncrypt.Write(plainText);
swEncrypt.Close();
return Convert.ToBase64String(msEncrypt.ToArray());
}
private string DecryptWithAes(string encryptedText)
{
byte[] cipherBytes = Convert.FromBase64String(encryptedText);
using var aes = Aes.Create();
aes.Key = _key;
aes.IV = _iv;
using var decryptor = aes.CreateDecryptor();
using var msDecrypt = new MemoryStream(cipherBytes);
using var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
using var srDecrypt = new StreamReader(csDecrypt);
return srDecrypt.ReadToEnd();
}
}
View File
@@ -0,0 +1,269 @@
using CredentialManager.Models;
using System.Text.Json;
namespace CredentialManager.Utilities;
/// <summary>
/// Metodi di utilità per la validazione delle credenziali
/// </summary>
public static class CredentialValidator
{
/// <summary>
/// Valida una credenziale database
/// </summary>
/// <param name="credential">La credenziale da validare</param>
/// <returns>Risultato della validazione</returns>
public static ValidationResult ValidateDatabaseCredential(DatabaseCredential credential)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(credential.Name))
errors.Add("Name is required");
if (string.IsNullOrWhiteSpace(credential.Host))
errors.Add("Host is required");
if (credential.Port <= 0)
errors.Add("Port must be greater than 0");
if (string.IsNullOrWhiteSpace(credential.DatabaseName))
errors.Add("Database name is required");
if (string.IsNullOrWhiteSpace(credential.Username))
errors.Add("Username is required");
if (string.IsNullOrWhiteSpace(credential.Password))
errors.Add("Password is required");
// Validate port ranges for specific database types
switch (credential.DatabaseType)
{
case DatabaseType.SqlServer:
if (credential.Port < 1 || credential.Port > 65535)
errors.Add("SQL Server port must be between 1 and 65535");
break;
case DatabaseType.MySql:
if (credential.Port < 1 || credential.Port > 65535)
errors.Add("MySQL port must be between 1 and 65535");
break;
case DatabaseType.PostgreSql:
if (credential.Port < 1 || credential.Port > 65535)
errors.Add("PostgreSQL port must be between 1 and 65535");
break;
case DatabaseType.Oracle:
if (credential.Port < 1 || credential.Port > 65535)
errors.Add("Oracle port must be between 1 and 65535");
break;
}
return new ValidationResult { IsValid = errors.Count == 0, Errors = errors };
}
/// <summary>
/// Valida una credenziale REST API
/// </summary>
/// <param name="credential">La credenziale da validare</param>
/// <returns>Risultato della validazione</returns>
public static ValidationResult ValidateRestApiCredential(RestApiCredential credential)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(credential.Name))
errors.Add("Name is required");
if (string.IsNullOrWhiteSpace(credential.BaseUrl))
errors.Add("Base URL is required");
if (!string.IsNullOrWhiteSpace(credential.BaseUrl) && !Uri.IsWellFormedUriString(credential.BaseUrl, UriKind.Absolute))
errors.Add("Base URL must be a valid absolute URI");
if (credential.TimeoutSeconds <= 0)
errors.Add("Timeout must be greater than 0 seconds");
// Check that at least one authentication method is provided
bool hasAuth = !string.IsNullOrWhiteSpace(credential.ApiKey) ||
!string.IsNullOrWhiteSpace(credential.AuthToken) ||
!string.IsNullOrWhiteSpace(credential.BearerToken) ||
(!string.IsNullOrWhiteSpace(credential.Username) && !string.IsNullOrWhiteSpace(credential.Password));
if (!hasAuth)
errors.Add("At least one authentication method must be provided (ApiKey, AuthToken, BearerToken, or Username/Password)");
return new ValidationResult { IsValid = errors.Count == 0, Errors = errors };
}
}
/// <summary>
/// Risultato di una validazione
/// </summary>
public class ValidationResult
{
public bool IsValid { get; set; }
public List<string> Errors { get; set; } = new();
}
/// <summary>
/// Metodi di utilità per l'import/export delle credenziali
/// </summary>
public static class CredentialImportExport
{
/// <summary>
/// Esporta le credenziali in formato JSON
/// </summary>
/// <param name="databaseCredentials">Credenziali database</param>
/// <param name="restApiCredentials">Credenziali REST API</param>
/// <returns>JSON string</returns>
public static string ExportToJson(
IEnumerable<DatabaseCredential> databaseCredentials,
IEnumerable<RestApiCredential> restApiCredentials)
{
var export = new
{
ExportDate = DateTime.UtcNow,
DatabaseCredentials = databaseCredentials,
RestApiCredentials = restApiCredentials
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
return JsonSerializer.Serialize(export, options);
}
/// <summary>
/// Importa le credenziali da formato JSON
/// </summary>
/// <param name="json">JSON string</param>
/// <returns>Tuple con le credenziali importate</returns>
public static (List<DatabaseCredential> DatabaseCredentials, List<RestApiCredential> RestApiCredentials) ImportFromJson(string json)
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
var databaseCredentials = new List<DatabaseCredential>();
var restApiCredentials = new List<RestApiCredential>();
if (root.TryGetProperty("databaseCredentials", out var dbElement))
{
databaseCredentials = JsonSerializer.Deserialize<List<DatabaseCredential>>(dbElement.GetRawText(), options) ?? new();
}
if (root.TryGetProperty("restApiCredentials", out var restElement))
{
restApiCredentials = JsonSerializer.Deserialize<List<RestApiCredential>>(restElement.GetRawText(), options) ?? new();
}
return (databaseCredentials, restApiCredentials);
}
}
/// <summary>
/// Metodi di utilità per la generazione di credenziali di test
/// </summary>
public static class CredentialTestDataGenerator
{
/// <summary>
/// Genera credenziali database di esempio per testing
/// </summary>
/// <returns>Lista di credenziali database di test</returns>
public static List<DatabaseCredential> GenerateTestDatabaseCredentials()
{
return new List<DatabaseCredential>
{
new DatabaseCredential
{
Name = "Test SQL Server",
DatabaseType = DatabaseType.SqlServer,
Host = "localhost",
Port = 1433,
DatabaseName = "TestDB",
Username = "testuser",
Password = "testpass123",
CommandTimeout = 30
},
new DatabaseCredential
{
Name = "Test MySQL",
DatabaseType = DatabaseType.MySql,
Host = "localhost",
Port = 3306,
DatabaseName = "testdb",
Username = "testuser",
Password = "testpass123",
CommandTimeout = 30
},
new DatabaseCredential
{
Name = "Test PostgreSQL",
DatabaseType = DatabaseType.PostgreSql,
Host = "localhost",
Port = 5432,
DatabaseName = "testdb",
Username = "testuser",
Password = "testpass123",
CommandTimeout = 30
},
new DatabaseCredential
{
Name = "Test SQLite",
DatabaseType = DatabaseType.Sqlite,
Host = "",
Port = 0,
DatabaseName = "test.db",
Username = "",
Password = "",
CommandTimeout = 30
}
};
}
/// <summary>
/// Genera credenziali REST API di esempio per testing
/// </summary>
/// <returns>Lista di credenziali REST API di test</returns>
public static List<RestApiCredential> GenerateTestRestApiCredentials()
{
return new List<RestApiCredential>
{
new RestApiCredential
{
Name = "Test API with API Key",
BaseUrl = "https://api.example.com/v1",
ApiKey = "test_api_key_123",
TimeoutSeconds = 60,
Headers = new Dictionary<string, string>
{
{ "Accept", "application/json" },
{ "Content-Type", "application/json" }
}
},
new RestApiCredential
{
Name = "Test API with Bearer Token",
BaseUrl = "https://api.another-example.com",
BearerToken = "bearer_token_xyz789",
TimeoutSeconds = 120
},
new RestApiCredential
{
Name = "Test API with Basic Auth",
BaseUrl = "https://api.basic-auth-example.com",
Username = "testuser",
Password = "testpassword",
TimeoutSeconds = 90,
Headers = new Dictionary<string, string>
{
{ "User-Agent", "CredentialManager-Test/1.0" }
}
}
};
}
}