diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..84736b8 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run Data_Coupler", + "type": "shell", + "command": "dotnet", + "args": [ + "run", + "--project", + "Data_Coupler/Data_Coupler.csproj" + ], + "group": "build", + "isBackground": true, + "problemMatcher": [] + }, + { + "label": "Test Database Initialization", + "type": "shell", + "command": "dotnet", + "args": [ + "run" + ], + "group": "build", + "isBackground": true, + "problemMatcher": [], + "options": { + "cwd": "${workspaceFolder}/Data_Coupler" + } + } + ] +} \ No newline at end of file diff --git a/CredentialManager/CLI/CredentialManagerCLI.cs b/CredentialManager/CLI/CredentialManagerCLI.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/CLI/README.md b/CredentialManager/CLI/README.md new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/CredentialManager.csproj b/CredentialManager/CredentialManager.csproj index 125f4c9..d9f48b8 100644 --- a/CredentialManager/CredentialManager.csproj +++ b/CredentialManager/CredentialManager.csproj @@ -4,6 +4,17 @@ net9.0 enable enable - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/CredentialManager/CredentialManagerConfiguration.cs b/CredentialManager/CredentialManagerConfiguration.cs new file mode 100644 index 0000000..0eaee03 --- /dev/null +++ b/CredentialManager/CredentialManagerConfiguration.cs @@ -0,0 +1,129 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using CredentialManager.Data; +using CredentialManager.Services; + +namespace CredentialManager; + +/// +/// Classe per la configurazione del CredentialManager +/// +public static class CredentialManagerConfiguration +{ + /// + /// Registra i servizi del CredentialManager + /// + /// La collezione di servizi + /// Il percorso del database SQLite (opzionale, default: credentials.db) + /// La collezione di servizi + 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(options => + options.UseSqlite($"Data Source={databasePath}")); + + // Registra i servizi + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + return services; + } + + /// + /// Inizializza il database del CredentialManager + /// + /// Il provider di servizi + /// Task completato + public static async Task InitializeCredentialManagerAsync(this IServiceProvider serviceProvider) + { + using var scope = serviceProvider.CreateScope(); + var initializer = scope.ServiceProvider.GetRequiredService(); + await initializer.InitializeAsync(); + } +} + +/// +/// Factory per creare un'istanza standalone del CredentialManager +/// +public static class CredentialManagerFactory +{ + /// + /// Crea un'istanza standalone del CredentialManager + /// + /// Il percorso del database SQLite (opzionale) + /// Factory per il logging (opzionale) + /// Un'istanza di ICredentialService + public static async Task 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(); + } + + /// + /// Crea un'istanza standalone del CredentialManager con configurazione personalizzata + /// + /// Azione per configurare servizi aggiuntivi + /// Il percorso del database SQLite (opzionale) + /// Il provider di servizi configurato + public static async Task CreateServiceProviderAsync( + Action? 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; + } +} diff --git a/CredentialManager/Data/CredentialDbContext.cs b/CredentialManager/Data/CredentialDbContext.cs new file mode 100644 index 0000000..fea8c6d --- /dev/null +++ b/CredentialManager/Data/CredentialDbContext.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore; +using CredentialManager.Models; + +namespace CredentialManager.Data; + +/// +/// DbContext per la gestione delle credenziali +/// +public class CredentialDbContext : DbContext +{ + public DbSet Credentials { get; set; } + + public CredentialDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); // Configurazione della tabella Credentials + modelBuilder.Entity(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); + }); + } +} diff --git a/CredentialManager/Examples/AdvancedExample.cs b/CredentialManager/Examples/AdvancedExample.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Examples/ConsoleExample.cs b/CredentialManager/Examples/ConsoleExample.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Examples/Program.cs b/CredentialManager/Examples/Program.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Examples/README.md b/CredentialManager/Examples/README.md new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Examples/UsageExamples.cs b/CredentialManager/Examples/UsageExamples.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Integration/DataConnectionHelper.cs b/CredentialManager/Integration/DataConnectionHelper.cs new file mode 100644 index 0000000..3b4e4a1 --- /dev/null +++ b/CredentialManager/Integration/DataConnectionHelper.cs @@ -0,0 +1,212 @@ +using CredentialManager.Models; +using CredentialManager.Services; + +namespace CredentialManager.Integration; + +/// +/// Metodi di utilità per l'integrazione con DataConnection +/// +public static class DataConnectionHelper +{ + /// + /// Converte una DatabaseCredential in una stringa di connessione + /// + /// La credenziale database + /// Stringa di connessione pronta per l'uso + public static string ToConnectionString(this DatabaseCredential credential) + { + return ConnectionStringBuilder.BuildConnectionString(credential); + } + + /// + /// Crea le opzioni per RestServiceOptions dal progetto DataConnection + /// + /// La credenziale REST API + /// Oggetto con le opzioni per REST service + 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 + }; + } + + /// + /// Crea le opzioni per DbManagerOptions dal progetto DataConnection + /// + /// La credenziale database + /// Oggetto con le opzioni per DB manager + public static object ToDbManagerOptions(this DatabaseCredential credential) + { + return new + { + ServerConnectionString = credential.ToConnectionString(), + DatabaseName = credential.DatabaseName, + DatabaseType = credential.DatabaseType.ToString(), + CommandTimeout = credential.CommandTimeout + }; + } + + /// + /// Ottiene la porta predefinita per un tipo di database + /// + /// Tipo di database + /// Porta predefinita + 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 + }; + } + + /// + /// Valida una credenziale database + /// + /// La credenziale da validare + /// Lista di errori di validazione (vuota se valida) + public static List ValidateDatabaseCredential(DatabaseCredential credential) + { + var errors = new List(); + + 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; + } + + /// + /// Valida una credenziale REST API + /// + /// La credenziale da validare + /// Lista di errori di validazione (vuota se valida) + public static List ValidateRestApiCredential(RestApiCredential credential) + { + var errors = new List(); + + 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; + } +} + +/// +/// Estensioni per la gestione asincrona delle credenziali +/// +public static class CredentialServiceExtensions +{ + /// + /// Ottiene una credenziale database validata + /// + /// Il servizio credenziali + /// Nome della credenziale + /// Credenziale validata o null se non trovata + public static async Task 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; + } + + /// + /// Ottiene una credenziale REST API validata + /// + /// Il servizio credenziali + /// Nome della credenziale + /// Credenziale validata o null se non trovata + public static async Task 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; + } + + /// + /// Salva una credenziale database con validazione + /// + /// Il servizio credenziali + /// La credenziale da salvare + /// ID della credenziale salvata + public static async Task 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); + } + + /// + /// Salva una credenziale REST API con validazione + /// + /// Il servizio credenziali + /// La credenziale da salvare + /// ID della credenziale salvata + public static async Task 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); + } +} diff --git a/CredentialManager/Migrations/20250617_AddRestServiceType.cs b/CredentialManager/Migrations/20250617_AddRestServiceType.cs new file mode 100644 index 0000000..a792636 --- /dev/null +++ b/CredentialManager/Migrations/20250617_AddRestServiceType.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CredentialManager.Migrations +{ + /// + public partial class AddRestServiceType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "RestServiceType", + table: "Credentials", + type: "TEXT", + maxLength: 50, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "RestServiceType", + table: "Credentials"); + } + } +} diff --git a/CredentialManager/Models/CredentialEntity.cs b/CredentialManager/Models/CredentialEntity.cs new file mode 100644 index 0000000..b492c10 --- /dev/null +++ b/CredentialManager/Models/CredentialEntity.cs @@ -0,0 +1,72 @@ +using System.ComponentModel.DataAnnotations; + +namespace CredentialManager.Models; + +/// +/// Entità per memorizzare le credenziali nel database +/// +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; } + + /// + /// Password criptata + /// + 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; +} diff --git a/CredentialManager/Models/CredentialModels.cs b/CredentialManager/Models/CredentialModels.cs new file mode 100644 index 0000000..e063f63 --- /dev/null +++ b/CredentialManager/Models/CredentialModels.cs @@ -0,0 +1,288 @@ +namespace CredentialManager.Models; + +/// +/// Tipi di credenziali supportate +/// +public enum CredentialType +{ + Database, + RestApi, + OAuth, + ApiKey, + BasicAuth +} + +/// +/// Tipi di servizi REST specifici +/// +public enum RestServiceType +{ + Generic, + SapB1ServiceLayer, + Salesforce +} + +/// +/// Tipi di database supportati (allineato con DataConnection.Enums.DatabaseType) +/// +public enum DatabaseType +{ + SqlServer, + MySql, + PostgreSql, + Oracle, + Sqlite, + DB2, + SapHana +} + +/// +/// DTO per le credenziali database - completo per tutti i tipi di DB supportati +/// +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? AdditionalParameters { get; set; } +} + +/// +/// DTO per le credenziali REST API - allineato con RestServiceOptions e esteso per servizi specifici +/// +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? Headers { get; set; } + public Dictionary? 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; } +} + +/// +/// Credenziali specifiche per SAP Business One Service Layer +/// +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? AdditionalHeaders { get; set; } +} + +/// +/// Credenziali specifiche per Salesforce +/// +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; } +} + +/// +/// Factory per creare connection string per i diversi tipi di database +/// +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 + { + $"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 + { + $"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 + { + $"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(); + + // 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 + { + $"Data Source={credential.DatabaseName}" + }; + + AddAdditionalParameters(builder, credential.AdditionalParameters); + return string.Join(";", builder); + } + + private static string BuildDb2ConnectionString(DatabaseCredential credential) + { + var builder = new List + { + $"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 + { + $"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 builder, Dictionary? additionalParams) + { + if (additionalParams != null) + { + foreach (var param in additionalParams) + { + builder.Add($"{param.Key}={param.Value}"); + } + } + } +} diff --git a/CredentialManager/README.md b/CredentialManager/README.md new file mode 100644 index 0000000..6c7503c --- /dev/null +++ b/CredentialManager/README.md @@ -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 + { + ["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(); +``` + +## 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 + { + ["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 + { + ["Content-Type"] = "application/json", + ["Accept"] = "application/json" + }, + AdditionalParameters = new Dictionary + { + ["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 diff --git a/CredentialManager/Services/CredentialService.cs b/CredentialManager/Services/CredentialService.cs new file mode 100644 index 0000000..e976a7c --- /dev/null +++ b/CredentialManager/Services/CredentialService.cs @@ -0,0 +1,922 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using CredentialManager.Data; +using CredentialManager.Models; +using System.Text.Json; + +namespace CredentialManager.Services; + +/// +/// Interfaccia per il servizio di gestione credenziali +/// +public interface ICredentialService +{ + // Database Credentials + Task SaveDatabaseCredentialAsync(DatabaseCredential credential); + Task GetDatabaseCredentialAsync(string name); + Task GetDatabaseCredentialAsync(int id); + Task> GetAllDatabaseCredentialsAsync(); + + // REST API Credentials + Task SaveRestApiCredentialAsync(RestApiCredential credential); + Task GetRestApiCredentialAsync(string name); + Task GetRestApiCredentialAsync(int id); + Task> GetAllRestApiCredentialsAsync(); + + // SAP B1 Service Layer Credentials + Task SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential); + Task GetSapB1CredentialAsync(string name); + Task GetSapB1CredentialAsync(int id); + Task> GetAllSapB1CredentialsAsync(); + + // Salesforce Credentials + Task SaveSalesforceCredentialAsync(SalesforceCredential credential); + Task GetSalesforceCredentialAsync(string name); + Task GetSalesforceCredentialAsync(int id); + Task> GetAllSalesforceCredentialsAsync(); + + // Generic operations + Task DeleteCredentialAsync(int id); + Task DeleteCredentialAsync(string name); + Task> GetCredentialNamesAsync(CredentialType? type = null); +} + +/// +/// Servizio per la gestione delle credenziali +/// +public class CredentialService : ICredentialService +{ + private readonly CredentialDbContext _context; + private readonly IEncryptionService _encryptionService; + private readonly ILogger _logger; + + public CredentialService( + CredentialDbContext context, + IEncryptionService encryptionService, + ILogger logger) + { + _context = context; + _encryptionService = encryptionService; + _logger = logger; + } + + #region Database Credentials + + public async Task 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 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 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> 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 SaveRestApiCredentialAsync(RestApiCredential credential) + { + try + { + // Prepara i parametri aggiuntivi per includere i campi specifici del servizio + var additionalParams = new Dictionary(); + + // 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 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 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> 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 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 + { + ["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 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 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> 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 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 + { + ["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 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 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> 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 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 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> 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(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>(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(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>(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>(entity.AdditionalParameters); + if (additionalParams != null) + { + // Imposta i parametri aggiuntivi generici + credential.AdditionalParameters = new Dictionary(); + + // 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 + { + "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>(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>(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>(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 +} diff --git a/CredentialManager/Services/DatabaseInitializer.cs b/CredentialManager/Services/DatabaseInitializer.cs new file mode 100644 index 0000000..950b57e --- /dev/null +++ b/CredentialManager/Services/DatabaseInitializer.cs @@ -0,0 +1,167 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using CredentialManager.Data; + +namespace CredentialManager.Services; + +/// +/// Interfaccia per l'inizializzazione del database +/// +public interface IDatabaseInitializer +{ + Task InitializeAsync(); + Task DatabaseExistsAsync(); +} + +/// +/// Servizio per l'inizializzazione del database SQLite +/// +public class DatabaseInitializer : IDatabaseInitializer +{ + private readonly CredentialDbContext _context; + private readonly ILogger _logger; + + public DatabaseInitializer(CredentialDbContext context, ILogger 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 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 + { + 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; + } + } +} diff --git a/CredentialManager/Services/EncryptionService.cs b/CredentialManager/Services/EncryptionService.cs new file mode 100644 index 0000000..3e7393e --- /dev/null +++ b/CredentialManager/Services/EncryptionService.cs @@ -0,0 +1,139 @@ +using System.Security.Cryptography; +using System.Text; +using System.Runtime.InteropServices; + +namespace CredentialManager.Services; + +/// +/// Interfaccia per il servizio di crittografia +/// +public interface IEncryptionService +{ + string Encrypt(string plainText); + string Decrypt(string encryptedText); +} + +/// +/// Servizio per la crittografia delle password cross-platform +/// +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(); + } +} diff --git a/CredentialManager/Test/Program.cs b/CredentialManager/Test/Program.cs new file mode 100644 index 0000000..e69de29 diff --git a/CredentialManager/Utilities/CredentialUtilities.cs b/CredentialManager/Utilities/CredentialUtilities.cs new file mode 100644 index 0000000..4ce19d1 --- /dev/null +++ b/CredentialManager/Utilities/CredentialUtilities.cs @@ -0,0 +1,269 @@ +using CredentialManager.Models; +using System.Text.Json; + +namespace CredentialManager.Utilities; + +/// +/// Metodi di utilità per la validazione delle credenziali +/// +public static class CredentialValidator +{ + /// + /// Valida una credenziale database + /// + /// La credenziale da validare + /// Risultato della validazione + public static ValidationResult ValidateDatabaseCredential(DatabaseCredential credential) + { + var errors = new List(); + + 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 }; + } + + /// + /// Valida una credenziale REST API + /// + /// La credenziale da validare + /// Risultato della validazione + public static ValidationResult ValidateRestApiCredential(RestApiCredential credential) + { + var errors = new List(); + + 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 }; + } +} + +/// +/// Risultato di una validazione +/// +public class ValidationResult +{ + public bool IsValid { get; set; } + public List Errors { get; set; } = new(); +} + +/// +/// Metodi di utilità per l'import/export delle credenziali +/// +public static class CredentialImportExport +{ + /// + /// Esporta le credenziali in formato JSON + /// + /// Credenziali database + /// Credenziali REST API + /// JSON string + public static string ExportToJson( + IEnumerable databaseCredentials, + IEnumerable 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); + } + + /// + /// Importa le credenziali da formato JSON + /// + /// JSON string + /// Tuple con le credenziali importate + public static (List DatabaseCredentials, List 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(); + var restApiCredentials = new List(); + + if (root.TryGetProperty("databaseCredentials", out var dbElement)) + { + databaseCredentials = JsonSerializer.Deserialize>(dbElement.GetRawText(), options) ?? new(); + } + + if (root.TryGetProperty("restApiCredentials", out var restElement)) + { + restApiCredentials = JsonSerializer.Deserialize>(restElement.GetRawText(), options) ?? new(); + } + + return (databaseCredentials, restApiCredentials); + } +} + +/// +/// Metodi di utilità per la generazione di credenziali di test +/// +public static class CredentialTestDataGenerator +{ + /// + /// Genera credenziali database di esempio per testing + /// + /// Lista di credenziali database di test + public static List GenerateTestDatabaseCredentials() + { + return new List + { + 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 + } + }; + } + + /// + /// Genera credenziali REST API di esempio per testing + /// + /// Lista di credenziali REST API di test + public static List GenerateTestRestApiCredentials() + { + return new List + { + new RestApiCredential + { + Name = "Test API with API Key", + BaseUrl = "https://api.example.com/v1", + ApiKey = "test_api_key_123", + TimeoutSeconds = 60, + Headers = new Dictionary + { + { "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 + { + { "User-Agent", "CredentialManager-Test/1.0" } + } + } + }; + } +} diff --git a/DataConnection/CredentialManagement/Interfaces/IDataConnectionCredentialService.cs b/DataConnection/CredentialManagement/Interfaces/IDataConnectionCredentialService.cs new file mode 100644 index 0000000..87e2647 --- /dev/null +++ b/DataConnection/CredentialManagement/Interfaces/IDataConnectionCredentialService.cs @@ -0,0 +1,59 @@ +using CredentialManager.Models; + +namespace DataConnection.CredentialManagement.Interfaces; + +/// +/// Interfaccia per la gestione delle credenziali integrate con DataConnection +/// +public interface IDataConnectionCredentialService +{ + // Database credentials + Task GetDatabaseCredentialAsync(string name); + Task GetDatabaseCredentialAsync(int id); + Task> GetAllDatabaseCredentialsAsync(); + Task SaveDatabaseCredentialAsync(DatabaseCredential credential); + Task DeleteDatabaseCredentialAsync(int id); + Task DeleteDatabaseCredentialAsync(string name); + + // REST API credentials + Task GetRestApiCredentialAsync(string name); + Task GetRestApiCredentialAsync(int id); + Task> GetAllRestApiCredentialsAsync(); + Task SaveRestApiCredentialAsync(RestApiCredential credential); + Task DeleteRestApiCredentialAsync(int id); + Task DeleteRestApiCredentialAsync(string name); + + // SAP B1 Service Layer credentials + Task GetSapB1CredentialAsync(string name); + Task GetSapB1CredentialAsync(int id); + Task> GetAllSapB1CredentialsAsync(); + Task SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential); + Task DeleteSapB1CredentialAsync(int id); + Task DeleteSapB1CredentialAsync(string name); + + // Salesforce credentials + Task GetSalesforceCredentialAsync(string name); + Task GetSalesforceCredentialAsync(int id); + Task> GetAllSalesforceCredentialsAsync(); + Task SaveSalesforceCredentialAsync(SalesforceCredential credential); + Task DeleteSalesforceCredentialAsync(int id); + Task DeleteSalesforceCredentialAsync(string name); + + // DataConnection specific operations + Task GetConnectionStringAsync(string credentialName); + Task GetConnectionStringAsync(int credentialId); + Task GetDbManagerOptionsAsync(string credentialName); + Task GetDbManagerOptionsAsync(int credentialId); + Task GetRestServiceOptionsAsync(string credentialName); + Task GetRestServiceOptionsAsync(int credentialId); + + // Connection testing + Task<(bool Success, string Message)> TestDatabaseConnectionAsync(string credentialName); + Task<(bool Success, string Message)> TestDatabaseConnectionAsync(DatabaseCredential credential); + Task<(bool Success, string Message)> TestRestApiConnectionAsync(string credentialName); + Task<(bool Success, string Message)> TestRestApiConnectionAsync(RestApiCredential credential); + Task<(bool Success, string Message)> TestSapB1ConnectionAsync(string credentialName); + Task<(bool Success, string Message)> TestSapB1ConnectionAsync(SapB1ServiceLayerCredential credential); + Task<(bool Success, string Message)> TestSalesforceConnectionAsync(string credentialName); + Task<(bool Success, string Message)> TestSalesforceConnectionAsync(SalesforceCredential credential); +} diff --git a/DataConnection/CredentialManagement/Models/CredentialExtensions.cs b/DataConnection/CredentialManagement/Models/CredentialExtensions.cs new file mode 100644 index 0000000..6065ec9 --- /dev/null +++ b/DataConnection/CredentialManagement/Models/CredentialExtensions.cs @@ -0,0 +1,101 @@ +using CredentialManager.Models; + +namespace DataConnection.CredentialManagement.Models; + +/// +/// Extension methods per convertire le credenziali CredentialManager in oggetti DataConnection +/// +public static class CredentialExtensions +{ + /// + /// Converte il tipo di database da CredentialManager.Models.DatabaseType a DataConnection.Enums.DatabaseType + /// + public static DataConnection.Enums.DatabaseType ToDataConnectionDatabaseType(this CredentialManager.Models.DatabaseType credentialDbType) + { + return credentialDbType switch + { + CredentialManager.Models.DatabaseType.SqlServer => DataConnection.Enums.DatabaseType.SqlServer, + CredentialManager.Models.DatabaseType.MySql => DataConnection.Enums.DatabaseType.MySql, + CredentialManager.Models.DatabaseType.PostgreSql => DataConnection.Enums.DatabaseType.PostgreSql, + CredentialManager.Models.DatabaseType.Oracle => DataConnection.Enums.DatabaseType.Oracle, + CredentialManager.Models.DatabaseType.Sqlite => DataConnection.Enums.DatabaseType.Sqlite, + CredentialManager.Models.DatabaseType.DB2 => DataConnection.Enums.DatabaseType.DB2, + CredentialManager.Models.DatabaseType.SapHana => DataConnection.Enums.DatabaseType.SapHana, + _ => throw new NotSupportedException($"Database type {credentialDbType} not supported") + }; + } + + /// + /// Converte il tipo di database da DataConnection.Enums.DatabaseType a CredentialManager.Models.DatabaseType + /// + public static CredentialManager.Models.DatabaseType ToCredentialDatabaseType(this DataConnection.Enums.DatabaseType dataConnectionDbType) + { + return dataConnectionDbType switch + { + DataConnection.Enums.DatabaseType.SqlServer => CredentialManager.Models.DatabaseType.SqlServer, + DataConnection.Enums.DatabaseType.MySql => CredentialManager.Models.DatabaseType.MySql, + DataConnection.Enums.DatabaseType.PostgreSql => CredentialManager.Models.DatabaseType.PostgreSql, + DataConnection.Enums.DatabaseType.Oracle => CredentialManager.Models.DatabaseType.Oracle, + DataConnection.Enums.DatabaseType.Sqlite => CredentialManager.Models.DatabaseType.Sqlite, + DataConnection.Enums.DatabaseType.DB2 => CredentialManager.Models.DatabaseType.DB2, + DataConnection.Enums.DatabaseType.SapHana => CredentialManager.Models.DatabaseType.SapHana, + _ => throw new NotSupportedException($"Database type {dataConnectionDbType} not supported") + }; + } + + /// + /// Crea una DatabaseCredential da parametri di connessione + /// + public static DatabaseCredential CreateDatabaseCredential( + string name, + DataConnection.Enums.DatabaseType databaseType, + string host, + int port, + string databaseName, + string username, + string password, + int commandTimeout = 30, + bool ignoreSslErrors = false) + { + return new DatabaseCredential + { + Name = name, + DatabaseType = databaseType.ToCredentialDatabaseType(), + Host = host, + Port = port, + DatabaseName = databaseName, + Username = username, + Password = password, + CommandTimeout = commandTimeout, + IgnoreSslErrors = ignoreSslErrors + }; + } + + /// + /// Crea una RestApiCredential da parametri di connessione + /// + public static RestApiCredential CreateRestApiCredential( + string name, + string baseUrl, + string? apiKey = null, + string? username = null, + string? password = null, + string? authToken = null, + int timeoutSeconds = 100, + bool ignoreSslErrors = false, + Dictionary? headers = null) + { + return new RestApiCredential + { + Name = name, + BaseUrl = baseUrl, + ApiKey = apiKey, + Username = username, + Password = password, + AuthToken = authToken, + TimeoutSeconds = timeoutSeconds, + IgnoreSslErrors = ignoreSslErrors, + Headers = headers + }; + } +} diff --git a/DataConnection/CredentialManagement/Models/CredentialExtensions2.cs b/DataConnection/CredentialManagement/Models/CredentialExtensions2.cs new file mode 100644 index 0000000..e69de29 diff --git a/DataConnection/CredentialManagement/Models/CredentialExtensions_New.cs b/DataConnection/CredentialManagement/Models/CredentialExtensions_New.cs new file mode 100644 index 0000000..e69de29 diff --git a/DataConnection/CredentialManagement/ServiceCollectionExtensions.cs b/DataConnection/CredentialManagement/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f823dab --- /dev/null +++ b/DataConnection/CredentialManagement/ServiceCollectionExtensions.cs @@ -0,0 +1,103 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using CredentialManager; +using CredentialManager.Services; +using CredentialManager.Data; +using DataConnection.CredentialManagement.Interfaces; +using DataConnection.CredentialManagement.Services; + +namespace DataConnection.CredentialManagement; + +/// +/// Metodi di estensione per configurare i servizi di gestione credenziali per DataConnection +/// +public static class ServiceCollectionExtensions +{ + /// + /// Aggiunge i servizi di gestione credenziali a DataConnection + /// + /// La collezione di servizi + /// Stringa di connessione per il database delle credenziali + /// La collezione di servizi per il chaining + public static IServiceCollection AddDataConnectionCredentialManagement( + this IServiceCollection services, + string connectionString = "Data Source=credentials.db") + { + // Estrai il percorso del database dalla connection string + string databasePath; + if (connectionString.StartsWith("Data Source=", StringComparison.OrdinalIgnoreCase)) + { + databasePath = connectionString.Substring("Data Source=".Length); + } + else + { + // Se non è una connection string, assumiamo che sia già un percorso + databasePath = connectionString; + } + + // Aggiungi i servizi base di CredentialManager + services.AddCredentialManager(databasePath); + + // Aggiungi il servizio di integrazione DataConnection + services.AddScoped(); + + return services; + } + + /// + /// Aggiunge i servizi di gestione credenziali con configurazione avanzata + /// + /// La collezione di servizi + /// Azione per configurare le opzioni + /// La collezione di servizi per il chaining + public static IServiceCollection AddDataConnectionCredentialManagement( + this IServiceCollection services, + Action configure) + { + var options = new DataConnectionCredentialOptions(); + configure(options); + + return services.AddDataConnectionCredentialManagement(options.ConnectionString); + } +} + +/// +/// Opzioni per la configurazione del DataConnectionCredentialManagement +/// +public class DataConnectionCredentialOptions +{ + /// + /// Stringa di connessione per il database delle credenziali + /// + public string ConnectionString { get; set; } = "Data Source=credentials.db"; + + /// + /// Abilita il logging dettagliato + /// + public bool EnableDetailedLogging { get; set; } = false; + + /// + /// Timeout per le operazioni sul database (in secondi) + /// + public int DatabaseTimeout { get; set; } = 30; +} + +/// +/// Interfaccia per il servizio di gestione credenziali specifico per DataConnection +/// Questa interfaccia estende le funzionalità base di CredentialManager +/// con metodi specifici per l'integrazione con DataConnection +/// +public interface IDataConnectionCredentialServiceConfiguration +{ + /// + /// Configura il servizio con le opzioni specificate + /// + /// Le opzioni di configurazione + void Configure(DataConnectionCredentialOptions options); + + /// + /// Verifica la connessione al database delle credenziali + /// + /// True se la connessione è valida + Task TestConnectionAsync(); +} diff --git a/DataConnection/CredentialManagement/ServiceCollectionExtensions_New.cs b/DataConnection/CredentialManagement/ServiceCollectionExtensions_New.cs new file mode 100644 index 0000000..e69de29 diff --git a/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs new file mode 100644 index 0000000..0c909f0 --- /dev/null +++ b/DataConnection/CredentialManagement/Services/DataConnectionCredentialService.cs @@ -0,0 +1,858 @@ +using CredentialManager.Models; +using CredentialManager.Services; +using CredentialManager.Integration; +using DataConnection.CredentialManagement.Interfaces; +using DataConnection.CredentialManagement.Models; +using Microsoft.Extensions.Logging; +using Microsoft.Data.SqlClient; +using Microsoft.Data.Sqlite; + +namespace DataConnection.CredentialManagement.Services; + +/// +/// Servizio per l'integrazione delle credenziali con DataConnection +/// +public class DataConnectionCredentialService : IDataConnectionCredentialService +{ + private readonly ICredentialService _credentialService; + private readonly ILogger _logger; + + public DataConnectionCredentialService( + ICredentialService credentialService, + ILogger logger) + { + _credentialService = credentialService; + _logger = logger; + } + + #region Database Credentials + + public async Task GetDatabaseCredentialAsync(string name) + { + return await _credentialService.GetDatabaseCredentialAsync(name); + } + + public async Task GetDatabaseCredentialAsync(int id) + { + return await _credentialService.GetDatabaseCredentialAsync(id); + } + + public async Task> GetAllDatabaseCredentialsAsync() + { + return await _credentialService.GetAllDatabaseCredentialsAsync(); + } + + public async Task SaveDatabaseCredentialAsync(DatabaseCredential credential) + { + _logger.LogInformation("Saving database credential: {Name}", credential.Name); + return await _credentialService.SaveDatabaseCredentialAsync(credential); + } + + public async Task DeleteDatabaseCredentialAsync(int id) + { + _logger.LogInformation("Deleting database credential with ID: {Id}", id); + return await _credentialService.DeleteCredentialAsync(id); + } + + public async Task DeleteDatabaseCredentialAsync(string name) + { + _logger.LogInformation("Deleting database credential: {Name}", name); + return await _credentialService.DeleteCredentialAsync(name); + } + + #endregion + + #region REST API Credentials + + public async Task GetRestApiCredentialAsync(string name) + { + return await _credentialService.GetRestApiCredentialAsync(name); + } + + public async Task GetRestApiCredentialAsync(int id) + { + return await _credentialService.GetRestApiCredentialAsync(id); + } + + public async Task> GetAllRestApiCredentialsAsync() + { + return await _credentialService.GetAllRestApiCredentialsAsync(); + } + + public async Task SaveRestApiCredentialAsync(RestApiCredential credential) + { + _logger.LogInformation("Saving REST API credential: {Name}", credential.Name); + return await _credentialService.SaveRestApiCredentialAsync(credential); + } + + public async Task DeleteRestApiCredentialAsync(int id) + { + _logger.LogInformation("Deleting REST API credential with ID: {Id}", id); + return await _credentialService.DeleteCredentialAsync(id); + } + + public async Task DeleteRestApiCredentialAsync(string name) + { + _logger.LogInformation("Deleting REST API credential: {Name}", name); + return await _credentialService.DeleteCredentialAsync(name); + } + + #endregion + + #region DataConnection Specific Operations + + public async Task GetConnectionStringAsync(string credentialName) + { + var credential = await GetDatabaseCredentialAsync(credentialName); + if (credential == null) + throw new InvalidOperationException($"Database credential '{credentialName}' not found"); + + return credential.ToConnectionString(); + } + + public async Task GetConnectionStringAsync(int credentialId) + { + var credential = await GetDatabaseCredentialAsync(credentialId); + if (credential == null) + throw new InvalidOperationException($"Database credential with ID '{credentialId}' not found"); return credential.ToConnectionString(); + } + public async Task GetDbManagerOptionsAsync(string credentialName) + { + var credential = await GetDatabaseCredentialAsync(credentialName); + if (credential == null) + throw new InvalidOperationException($"Database credential '{credentialName}' not found"); + + var options = new DataConnection.EF.DbManagerOptions + { + ServerConnectionString = credential.ToConnectionString(), + DatabaseName = credential.DatabaseName!, + DatabaseType = credential.DatabaseType.ToDataConnectionDatabaseType(), + CommandTimeout = credential.CommandTimeout + }; + + // Configura automaticamente il servizio di scoperta database + options.ConfigureDatabaseDiscovery(options.DatabaseType); + + _logger.LogDebug("Created DbManagerOptions for credential: {Name} ({DatabaseType})", + credentialName, credential.DatabaseType); + + return options; + } + + public async Task GetDbManagerOptionsAsync(int credentialId) + { + var credential = await GetDatabaseCredentialAsync(credentialId); + if (credential == null) + throw new InvalidOperationException($"Database credential with ID '{credentialId}' not found"); var options = new DataConnection.EF.DbManagerOptions + { + ServerConnectionString = credential.ToConnectionString(), + DatabaseName = credential.DatabaseName!, + DatabaseType = credential.DatabaseType.ToDataConnectionDatabaseType(), + CommandTimeout = credential.CommandTimeout + }; + + // Configura automaticamente il servizio di scoperta database + options.ConfigureDatabaseDiscovery(options.DatabaseType); + + _logger.LogDebug("Created DbManagerOptions for credential ID: {Id} ({DatabaseType})", + credentialId, credential.DatabaseType); + return options; + } + + public async Task GetRestServiceOptionsAsync(string credentialName) + { + var credential = await GetRestApiCredentialAsync(credentialName); + if (credential == null) + throw new InvalidOperationException($"REST API credential '{credentialName}' not found"); + + var options = new DataConnection.REST.Configuration.RestServiceOptions + { + BaseUrl = credential.BaseUrl, + ApiKey = credential.ApiKey, + Username = credential.Username, + Password = credential.Password, + AuthToken = credential.AuthToken, + TimeoutSeconds = credential.TimeoutSeconds, + IgnoreSslErrors = credential.IgnoreSslErrors + }; + + _logger.LogDebug("Created RestServiceOptions for credential: {Name} ({BaseUrl})", + credentialName, credential.BaseUrl); + + return options; + } + + public async Task GetRestServiceOptionsAsync(int credentialId) + { + var credential = await GetRestApiCredentialAsync(credentialId); + if (credential == null) + throw new InvalidOperationException($"REST API credential with ID '{credentialId}' not found"); + + var options = new DataConnection.REST.Configuration.RestServiceOptions + { + BaseUrl = credential.BaseUrl, + ApiKey = credential.ApiKey, + Username = credential.Username, + Password = credential.Password, + AuthToken = credential.AuthToken, + TimeoutSeconds = credential.TimeoutSeconds, + IgnoreSslErrors = credential.IgnoreSslErrors + }; + + _logger.LogDebug("Created RestServiceOptions for credential ID: {Id} ({BaseUrl})", + credentialId, credential.BaseUrl); + return options; + } + + #endregion + + #region Connection Testing + + public async Task<(bool Success, string Message)> TestDatabaseConnectionAsync(string credentialName) + { + try + { + var credential = await GetDatabaseCredentialAsync(credentialName); + if (credential == null) + return (false, $"Credenziale '{credentialName}' non trovata"); + + return await TestDatabaseConnectionAsync(credential); + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione per credenziale: {Name}", credentialName); + return (false, $"Errore nel test: {ex.Message}"); + } + } + public async Task<(bool Success, string Message)> TestDatabaseConnectionAsync(DatabaseCredential credential) + { + try + { + var connectionString = ConnectionStringBuilder.BuildConnectionString(credential); + + // Test base della sintassi + if (string.IsNullOrEmpty(connectionString)) + return (false, "Connection string vuota o non valida"); + + _logger.LogInformation("Testing database connection for {Name} ({DatabaseType})", + credential.Name, credential.DatabaseType); + + // Test di connessione diretto basato sul tipo di database + try + { + return credential.DatabaseType switch + { + CredentialManager.Models.DatabaseType.SqlServer => await TestSqlServerConnection(connectionString, credential), + CredentialManager.Models.DatabaseType.MySql => await TestMySqlConnection(connectionString, credential), + CredentialManager.Models.DatabaseType.PostgreSql => await TestPostgreSqlConnection(connectionString, credential), + CredentialManager.Models.DatabaseType.Oracle => await TestOracleConnection(connectionString, credential), + CredentialManager.Models.DatabaseType.Sqlite => await TestSqliteConnection(connectionString, credential), + _ => (false, $"Test di connessione non implementato per {credential.DatabaseType}") + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Database connection test failed for {Name}", credential.Name); + return (false, $"Errore di connessione: {ex.Message}"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione database"); + return (false, $"Errore: {ex.Message}"); + } + } + + private async Task<(bool Success, string Message)> TestSqlServerConnection(string connectionString, DatabaseCredential credential) + { + try + { + using var connection = new Microsoft.Data.SqlClient.SqlConnection(connectionString); + connection.Open(); + + var command = connection.CreateCommand(); + command.CommandText = "SELECT @@VERSION"; + command.CommandTimeout = credential.CommandTimeout; + var version = await command.ExecuteScalarAsync(); + + return (true, $"Connessione SQL Server riuscita!\n\nDettagli:\n- Host: {credential.Host}:{credential.Port}\n- Database: {credential.DatabaseName ?? "Default"}\n- Versione: {version?.ToString()?.Split('\n')[0]}\n- Timeout: {credential.CommandTimeout}s"); + } + catch (Exception ex) + { + return (false, $"Errore SQL Server: {ex.Message}"); + } + } + private Task<(bool Success, string Message)> TestMySqlConnection(string connectionString, DatabaseCredential credential) + { + try + { + // Per MySQL, dovremmo usare MySql.Data.MySqlClient o Pomelo.EntityFrameworkCore.MySql + // Per ora returnamo un messaggio che indica che il test non è implementato + return Task.FromResult<(bool Success, string Message)>((false, "Test MySQL non implementato - installare MySql.Data.MySqlClient")); + } + catch (Exception ex) + { + return Task.FromResult<(bool Success, string Message)>((false, $"Errore MySQL: {ex.Message}")); + } + } + + private Task<(bool Success, string Message)> TestPostgreSqlConnection(string connectionString, DatabaseCredential credential) + { + try + { + // Per PostgreSQL, dovremmo usare Npgsql + return Task.FromResult<(bool Success, string Message)>((false, "Test PostgreSQL non implementato - installare Npgsql")); + } + catch (Exception ex) + { + return Task.FromResult<(bool Success, string Message)>((false, $"Errore PostgreSQL: {ex.Message}")); + } + } + + private Task<(bool Success, string Message)> TestOracleConnection(string connectionString, DatabaseCredential credential) + { + try + { + // Per Oracle, dovremmo usare Oracle.ManagedDataAccess + return Task.FromResult<(bool Success, string Message)>((false, "Test Oracle non implementato - installare Oracle.ManagedDataAccess")); + } + catch (Exception ex) + { + return Task.FromResult<(bool Success, string Message)>((false, $"Errore Oracle: {ex.Message}")); + } + } + + private async Task<(bool Success, string Message)> TestSqliteConnection(string connectionString, DatabaseCredential credential) + { + try + { + using var connection = new Microsoft.Data.Sqlite.SqliteConnection(connectionString); + connection.Open(); + + var command = connection.CreateCommand(); + command.CommandText = "SELECT sqlite_version()"; + command.CommandTimeout = credential.CommandTimeout; + var version = await command.ExecuteScalarAsync(); + + return (true, $"Connessione SQLite riuscita!\n\nDettagli:\n- File: {credential.Host ?? connectionString}\n- Versione SQLite: {version}\n- Timeout: {credential.CommandTimeout}s"); + } + catch (Exception ex) + { + return (false, $"Errore SQLite: {ex.Message}"); + } + } + public async Task<(bool Success, string Message)> TestRestApiConnectionAsync(string credentialName) + { + try + { + _logger.LogInformation("Attempting to test REST API connection for credential: {Name}", credentialName); + + var credential = await GetRestApiCredentialAsync(credentialName); + if (credential == null) + { + _logger.LogWarning("REST API credential '{Name}' not found", credentialName); + return (false, $"Credenziale REST API '{credentialName}' non trovata"); + } + + _logger.LogInformation("Found credential: {Name}, ServiceType: {ServiceType}, BaseUrl: {BaseUrl}", + credential.Name, credential.ServiceType, credential.BaseUrl); + + return await TestRestApiConnectionAsync(credential); + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione REST API per credenziale: {Name}", credentialName); + return (false, $"Errore nel test: {ex.Message}"); + } + } + public async Task<(bool Success, string Message)> TestRestApiConnectionAsync(RestApiCredential credential) + { + try + { + if (string.IsNullOrEmpty(credential.BaseUrl)) + return (false, "Base URL non specificata"); + + // Test base dell'URL + if (!Uri.TryCreate(credential.BaseUrl, UriKind.Absolute, out var uri)) + return (false, "Base URL non valida"); + + // Per i servizi specifici, eseguiamo test di autenticazione reali + switch (credential.ServiceType) + { + case RestServiceType.SapB1ServiceLayer: + return await TestSapB1ServiceLayerAuthentication(credential); + + case RestServiceType.Salesforce: + return await TestSalesforceAuthentication(credential); + + default: + // Per REST API generiche, facciamo un test di connettività base + return await TestGenericRestApiConnection(credential); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione REST API"); + return (false, $"Errore: {ex.Message}"); + } + } + + private async Task<(bool Success, string Message)> TestSapB1ServiceLayerAuthentication(RestApiCredential credential) + { + try + { + _logger.LogInformation("Testing SAP B1 Service Layer authentication for {Name} ({BaseUrl})", + credential.Name, credential.BaseUrl); + + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds); + + // Test di login al Service Layer + var loginUrl = credential.BaseUrl.TrimEnd('/') + "/Login"; + + var loginData = new + { + CompanyDB = credential.CompanyDatabase, + UserName = credential.Username, + Password = credential.Password, + Language = credential.Language ?? "en-US" + }; + + var loginJson = System.Text.Json.JsonSerializer.Serialize(loginData); + var loginContent = new StringContent(loginJson, System.Text.Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync(loginUrl, loginContent); + + if (response.IsSuccessStatusCode) + { + return (true, $"Autenticazione SAP B1 Service Layer riuscita!\n\nDettagli:\n- Server: {credential.BaseUrl}\n- Company DB: {credential.CompanyDatabase}\n- Versione: {credential.Version}\n- Lingua: {credential.Language}\n- Timeout: {credential.TimeoutSeconds}s"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + return (false, $"Autenticazione SAP B1 fallita. Status: {response.StatusCode}\nDettagli: {errorContent}"); + } + } + catch (HttpRequestException ex) + { + return (false, $"Errore di rete SAP B1: {ex.Message}"); + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout")) + { + return (false, $"Timeout della connessione SAP B1 ({credential.TimeoutSeconds}s)"); + } + } + private async Task<(bool Success, string Message)> TestSalesforceAuthentication(RestApiCredential credential) + { + try + { + _logger.LogInformation("Testing Salesforce authentication for {Name} ({BaseUrl})", + credential.Name, credential.BaseUrl); + + _logger.LogDebug("Salesforce credential details: Username={Username}, HasPassword={HasPassword}, HasSecurityToken={HasSecurityToken}, HasClientId={HasClientId}, HasClientSecret={HasClientSecret}", + credential.Username, !string.IsNullOrEmpty(credential.Password), !string.IsNullOrEmpty(credential.SecurityToken), + !string.IsNullOrEmpty(credential.ClientId), !string.IsNullOrEmpty(credential.ClientSecret)); + + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds); + + // Test di autenticazione OAuth2 + var tokenUrl = credential.BaseUrl.TrimEnd('/') + "/services/oauth2/token"; + var tokenData = new List> + { + new("grant_type", "password"), + new("username", credential.Username ?? "") + }; + + // Aggiungiamo password + security token se disponibile + var password = credential.Password ?? ""; + if (!string.IsNullOrEmpty(credential.SecurityToken)) + { + password += credential.SecurityToken; + } + tokenData.Add(new("password", password)); + + // Aggiungiamo client credentials se disponibili + if (!string.IsNullOrEmpty(credential.ClientId)) + { + tokenData.Add(new("client_id", credential.ClientId)); + } + if (!string.IsNullOrEmpty(credential.ClientSecret)) + { + tokenData.Add(new("client_secret", credential.ClientSecret)); + } + + _logger.LogDebug("Posting to Salesforce token URL: {TokenUrl}", tokenUrl); + + var tokenContent = new FormUrlEncodedContent(tokenData); + var response = await httpClient.PostAsync(tokenUrl, tokenContent); + + if (response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("Salesforce authentication successful for {Name}", credential.Name); + return (true, $"Autenticazione Salesforce riuscita!\n\nDettagli:\n- Login URL: {credential.BaseUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: OAuth2\n- Timeout: {credential.TimeoutSeconds}s"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + _logger.LogWarning("Salesforce authentication failed for {Name}. Status: {StatusCode}, Response: {Response}", + credential.Name, response.StatusCode, errorContent); + return (false, $"Autenticazione Salesforce fallita. Status: {response.StatusCode}\nDettagli: {errorContent}"); + } + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Network error during Salesforce authentication for {Name}", credential.Name); + return (false, $"Errore di rete Salesforce: {ex.Message}"); + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout")) + { + _logger.LogWarning("Timeout during Salesforce authentication for {Name} ({TimeoutSeconds}s)", credential.Name, credential.TimeoutSeconds); + return (false, $"Timeout della connessione Salesforce ({credential.TimeoutSeconds}s)"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error during Salesforce authentication for {Name}", credential.Name); + return (false, $"Errore imprevisto: {ex.Message}"); + } + } + + private async Task<(bool Success, string Message)> TestGenericRestApiConnection(RestApiCredential credential) + { + try + { + // Test di connessione reale per REST API generiche + using var httpClient = new HttpClient(); + + // Configurazione del timeout + httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds); + + // Configurazione autenticazione + if (!string.IsNullOrEmpty(credential.ApiKey)) + { + httpClient.DefaultRequestHeaders.Add("X-API-Key", credential.ApiKey); + } + else if (!string.IsNullOrEmpty(credential.AuthToken)) + { + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {credential.AuthToken}"); + } + else if (!string.IsNullOrEmpty(credential.Username) && !string.IsNullOrEmpty(credential.Password)) + { + var authValue = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{credential.Username}:{credential.Password}")); + httpClient.DefaultRequestHeaders.Add("Authorization", $"Basic {authValue}"); + } + + _logger.LogInformation("Testing generic REST API connection for {Name} ({BaseUrl})", + credential.Name, credential.BaseUrl); + + // Facciamo una richiesta HEAD o GET semplice per testare la connettività + var testEndpoint = credential.BaseUrl.TrimEnd('/') + "/"; + + var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, testEndpoint)); + + // Anche un 404 o 401 indica che il server è raggiungibile + var isReachable = response.StatusCode != System.Net.HttpStatusCode.RequestTimeout && + !IsNetworkError(response.StatusCode); + + if (isReachable) + { + return (true, $"Connessione REST API riuscita!\n\nDettagli:\n- URL: {credential.BaseUrl}\n- Status: {response.StatusCode}\n- Timeout: {credential.TimeoutSeconds}s\n- Auth: {GetAuthType(credential)}"); + } + else + { + return (false, $"Connessione fallita. Status: {response.StatusCode}\nVerifica URL e configurazione di rete."); + } + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "Generic REST API connection test failed for {Name}", credential.Name); + return (false, $"Errore di rete: {ex.Message}"); + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout")) + { + return (false, $"Timeout della connessione ({credential.TimeoutSeconds}s). Il server potrebbe essere irraggiungibile."); + } + } + + private static bool IsNetworkError(System.Net.HttpStatusCode statusCode) + { + return statusCode == System.Net.HttpStatusCode.RequestTimeout || + statusCode == System.Net.HttpStatusCode.BadGateway || + statusCode == System.Net.HttpStatusCode.ServiceUnavailable || + statusCode == System.Net.HttpStatusCode.GatewayTimeout; + } + + private static string GetAuthType(RestApiCredential credential) + { + if (!string.IsNullOrEmpty(credential.ApiKey)) + return "API Key"; + if (!string.IsNullOrEmpty(credential.AuthToken)) + return "Auth Token"; + if (!string.IsNullOrEmpty(credential.Username)) + return "Basic Auth"; + return "Nessuna"; + } + + #endregion + + #region SAP B1 Service Layer Credentials + + public async Task GetSapB1CredentialAsync(string name) + { + return await _credentialService.GetSapB1CredentialAsync(name); + } + + public async Task GetSapB1CredentialAsync(int id) + { + return await _credentialService.GetSapB1CredentialAsync(id); + } + + public async Task> GetAllSapB1CredentialsAsync() + { + return await _credentialService.GetAllSapB1CredentialsAsync(); + } + + public async Task SaveSapB1CredentialAsync(SapB1ServiceLayerCredential credential) + { + _logger.LogInformation("Saving SAP B1 Service Layer credential: {Name}", credential.Name); + return await _credentialService.SaveSapB1CredentialAsync(credential); + } + + public async Task DeleteSapB1CredentialAsync(int id) + { + _logger.LogInformation("Deleting SAP B1 Service Layer credential with ID: {Id}", id); + return await _credentialService.DeleteCredentialAsync(id); + } + + public async Task DeleteSapB1CredentialAsync(string name) + { + _logger.LogInformation("Deleting SAP B1 Service Layer credential: {Name}", name); + return await _credentialService.DeleteCredentialAsync(name); + } + + public async Task<(bool Success, string Message)> TestSapB1ConnectionAsync(string credentialName) + { + try + { + var credential = await GetSapB1CredentialAsync(credentialName); + if (credential == null) + return (false, $"Credenziale SAP B1 '{credentialName}' non trovata"); + + return await TestSapB1ConnectionAsync(credential); + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione SAP B1 per credenziale: {Name}", credentialName); + return (false, $"Errore nel test: {ex.Message}"); + } + } + + public async Task<(bool Success, string Message)> TestSapB1ConnectionAsync(SapB1ServiceLayerCredential credential) + { + try + { + if (string.IsNullOrEmpty(credential.ServerUrl)) + return (false, "Server URL non specificato"); + + if (!Uri.TryCreate(credential.ServerUrl, UriKind.Absolute, out var uri)) + return (false, "Server URL non valido"); + + _logger.LogInformation("Testing SAP B1 Service Layer connection for {Name} ({ServerUrl})", + credential.Name, credential.ServerUrl); + + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds); + + // Test di accesso al Service Layer + var loginUrl = credential.ServerUrl.TrimEnd('/') + "/Login"; + + var loginData = new + { + CompanyDB = credential.CompanyDatabase, + UserName = credential.Username, + Password = credential.Password, + Language = credential.Language ?? "en-US" + }; + + var loginJson = System.Text.Json.JsonSerializer.Serialize(loginData); + var loginContent = new StringContent(loginJson, System.Text.Encoding.UTF8, "application/json"); + + try + { + var response = await httpClient.PostAsync(loginUrl, loginContent); + + if (response.IsSuccessStatusCode) + { + return (true, $"Connessione SAP B1 Service Layer riuscita!\n\nDettagli:\n- Server: {credential.ServerUrl}\n- Company DB: {credential.CompanyDatabase}\n- Versione: {credential.Version}\n- Lingua: {credential.Language}\n- Timeout: {credential.TimeoutSeconds}s"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + return (false, $"Login fallito. Status: {response.StatusCode}\nDettagli: {errorContent}"); + } + } + catch (HttpRequestException ex) + { + return (false, $"Errore di rete SAP B1: {ex.Message}"); + } + catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException || ex.Message.Contains("timeout")) + { + return (false, $"Timeout della connessione SAP B1 ({credential.TimeoutSeconds}s)."); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione SAP B1"); + return (false, $"Errore: {ex.Message}"); + } + } + + #endregion + + #region Salesforce Credentials + + public async Task GetSalesforceCredentialAsync(string name) + { + return await _credentialService.GetSalesforceCredentialAsync(name); + } + + public async Task GetSalesforceCredentialAsync(int id) + { + return await _credentialService.GetSalesforceCredentialAsync(id); + } + + public async Task> GetAllSalesforceCredentialsAsync() + { + return await _credentialService.GetAllSalesforceCredentialsAsync(); + } + + public async Task SaveSalesforceCredentialAsync(SalesforceCredential credential) + { + _logger.LogInformation("Saving Salesforce credential: {Name}", credential.Name); + return await _credentialService.SaveSalesforceCredentialAsync(credential); + } + + public async Task DeleteSalesforceCredentialAsync(int id) + { + _logger.LogInformation("Deleting Salesforce credential with ID: {Id}", id); + return await _credentialService.DeleteCredentialAsync(id); + } + + public async Task DeleteSalesforceCredentialAsync(string name) + { + _logger.LogInformation("Deleting Salesforce credential: {Name}", name); + return await _credentialService.DeleteCredentialAsync(name); + } + + public async Task<(bool Success, string Message)> TestSalesforceConnectionAsync(string credentialName) + { + try + { + var credential = await GetSalesforceCredentialAsync(credentialName); + if (credential == null) + return (false, $"Credenziale Salesforce '{credentialName}' non trovata"); + + return await TestSalesforceConnectionAsync(credential); + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione Salesforce per credenziale: {Name}", credentialName); + return (false, $"Errore nel test: {ex.Message}"); + } + } + + public async Task<(bool Success, string Message)> TestSalesforceConnectionAsync(SalesforceCredential credential) + { + try + { + if (string.IsNullOrEmpty(credential.LoginUrl)) + return (false, "Login URL non specificato"); + + if (!Uri.TryCreate(credential.LoginUrl, UriKind.Absolute, out var uri)) + return (false, "Login URL non valido"); + + _logger.LogInformation("Testing Salesforce connection for {Name} ({LoginUrl})", + credential.Name, credential.LoginUrl); + + using var httpClient = new HttpClient(); + httpClient.Timeout = TimeSpan.FromSeconds(credential.TimeoutSeconds); + + // Test di login SOAP/REST + if (credential.UseSoapApi) + { + return await TestSalesforceSoapLogin(httpClient, credential); + } + else + { + return await TestSalesforceOAuthLogin(httpClient, credential); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Errore nel test della connessione Salesforce"); + return (false, $"Errore: {ex.Message}"); + } + } + + private async Task<(bool Success, string Message)> TestSalesforceOAuthLogin(HttpClient httpClient, SalesforceCredential credential) + { + try + { + var tokenUrl = credential.LoginUrl.TrimEnd('/') + "/services/oauth2/token"; + + var tokenData = new List> + { + new("grant_type", "password"), + new("username", credential.Username), + new("password", credential.Password + credential.SecurityToken), + new("client_id", credential.ClientId ?? ""), + new("client_secret", credential.ClientSecret ?? "") + }; + + var tokenContent = new FormUrlEncodedContent(tokenData); + var response = await httpClient.PostAsync(tokenUrl, tokenContent); + + if (response.IsSuccessStatusCode) + { + var responseContent = await response.Content.ReadAsStringAsync(); + return (true, $"Connessione Salesforce riuscita!\n\nDettagli:\n- Login URL: {credential.LoginUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: OAuth2\n- Timeout: {credential.TimeoutSeconds}s"); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + return (false, $"Login OAuth Salesforce fallito. Status: {response.StatusCode}\nDettagli: {errorContent}"); + } + } + catch (Exception ex) + { + return (false, $"Errore OAuth Salesforce: {ex.Message}"); + } + } + + private async Task<(bool Success, string Message)> TestSalesforceSoapLogin(HttpClient httpClient, SalesforceCredential credential) + { + try + { + // Test semplificato per SOAP - verifica solo la raggiungibilità del servizio + var soapUrl = credential.LoginUrl.TrimEnd('/') + "/services/Soap/u/" + credential.ApiVersion; + + var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, soapUrl)); + + if (response.IsSuccessStatusCode || response.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed) + { + return (true, $"Connessione Salesforce SOAP riuscita!\n\nDettagli:\n- SOAP URL: {soapUrl}\n- API Version: {credential.ApiVersion}\n- Sandbox: {credential.IsSandbox}\n- Tipo Auth: SOAP\n- Timeout: {credential.TimeoutSeconds}s"); + } + else + { + return (false, $"Servizio SOAP Salesforce non raggiungibile. Status: {response.StatusCode}"); + } + } + catch (Exception ex) + { + return (false, $"Errore SOAP Salesforce: {ex.Message}"); + } + } + + #endregion +} diff --git a/DataConnection/DB/EF/DbManagerOptions.cs b/DataConnection/DB/EF/DbManagerOptions.cs index 81c8f5b..3ca08fe 100644 --- a/DataConnection/DB/EF/DbManagerOptions.cs +++ b/DataConnection/DB/EF/DbManagerOptions.cs @@ -64,9 +64,7 @@ public class DbManagerOptions /// /// Tipo di database (SqlServer, MySql, ecc.) /// - public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer; - - /// + public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer; /// /// Configura automaticamente il servizio di scoperta database in base al tipo di database /// /// Tipo di database @@ -78,13 +76,14 @@ public class DbManagerOptions { case DatabaseType.SqlServer: DatabaseDiscoveryService = new SqlServerDatabaseDiscovery(); + DbContextConfigurator = options => options.UseSqlServer(BuildFullConnectionString(), + sqlOptions => sqlOptions.CommandTimeout(CommandTimeout)); break; - // case DatabaseType.MySql: - // DatabaseDiscoveryService = new MySqlDatabaseDiscovery(); - // break; - // Altri tipi di database possono essere aggiunti qui default: - throw new NotSupportedException($"Tipo di database non supportato: {databaseType}"); + // Per altri database, configuriamo un configuratore di base che non fa nulla + // Il test di connessione userà un approccio diverso + DbContextConfigurator = options => { }; + break; } } diff --git a/DataConnection/DataConnection.csproj b/DataConnection/DataConnection.csproj index cf13a89..6230e81 100644 --- a/DataConnection/DataConnection.csproj +++ b/DataConnection/DataConnection.csproj @@ -8,13 +8,18 @@ - - - + + + + + + + + diff --git a/Data_Coupler/Data Source=wwwroot/data/credentials.db b/Data_Coupler/Data Source=wwwroot/data/credentials.db new file mode 100644 index 0000000..c49af94 Binary files /dev/null and b/Data_Coupler/Data Source=wwwroot/data/credentials.db differ diff --git a/Data_Coupler/Pages/CredentialManagement.razor b/Data_Coupler/Pages/CredentialManagement.razor new file mode 100644 index 0000000..7abb04a --- /dev/null +++ b/Data_Coupler/Pages/CredentialManagement.razor @@ -0,0 +1,963 @@ +@page "/credentials" +@using CredentialManager.Models +@using DataConnection.CredentialManagement.Interfaces +@using DataConnection.CredentialManagement.Models +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.JSInterop +@inject IDataConnectionCredentialService CredentialService +@inject IJSRuntime JSRuntime + +Gestione Credenziali + +

Gestione Credenziali

+ +
+
+
+ + +
+ +
+
+ +@if (loading) +{ +
+
+ Caricamento... +
+
+} +else if (!string.IsNullOrEmpty(errorMessage)) +{ + +} +else +{ + +
+
+

Credenziali Database (@databaseCredentials.Count)

+ @if (databaseCredentials.Any()) + { +
+ + + + + + + + + + + + + @foreach (var credential in databaseCredentials) + { + + + + + + + + } + +
NomeTipo DatabaseHost:PortaDatabaseUsernameAzioni
@credential.Name@credential.DatabaseType@credential.Host:@credential.Port + @if (string.IsNullOrEmpty(credential.DatabaseName)) + { + Connessione server + } + else + { + @credential.DatabaseName + } + @credential.Username + + + +
+
+ } + else + { +
+ Nessuna credenziale database configurata. +
+ } +
+
+ +
+
+
+

Credenziali REST API / Servizi (@restApiCredentials.Count)

+ @if (restApiCredentials.Any()) + { +
+ + + + + + + + + + + + + @foreach (var credential in restApiCredentials) + { + + + + + + + + + } + +
NomeTipo ServizioBase URLAutenticazioneTimeout (s)Azioni
@credential.Name + @if (credential.ServiceType == RestServiceType.SapB1ServiceLayer) + { + SAP B1 + } + else if (credential.ServiceType == RestServiceType.Salesforce) + { + Salesforce + @if (credential.IsSandbox) + { + Sandbox + } + } + else + { + Generic REST + } + @credential.BaseUrl@GetAuthenticationType(credential)@credential.TimeoutSeconds + + + +
+
} + else + { +
+ Nessuna credenziale REST API configurata. +
+ } +
+
+} + + +@if (showDatabaseModal) +{ + +} + + +@if (showRestApiModal) +{ + +} + + +@code { private List databaseCredentials = new(); + private List restApiCredentials = new(); + private bool loading = true; + private string? errorMessage = null; + private bool testingConnection = false; + + // Modal state + private bool showDatabaseModal = false; + private bool showRestApiModal = false; + private DatabaseCredential? editingDatabaseCredential = null; + private RestApiCredential? editingRestApiCredential = null; + private DatabaseCredential currentDatabaseCredential = new(); + private RestApiCredential currentRestApiCredential = new(); + + protected override async Task OnInitializedAsync() + { + await RefreshCredentials(); + } private async Task RefreshCredentials() + { + loading = true; + errorMessage = null; + try + { + databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync(); + restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync(); + } + catch (Exception ex) + { + errorMessage = $"Errore nel caricamento delle credenziali: {ex.Message}"; + // Se l'errore è relativo alla tabella mancante, mostriamo un messaggio più specifico + if (ex.Message.Contains("no such table: Credentials")) + { + errorMessage = "Database non inizializzato correttamente. Riavviare l'applicazione."; + } + } + finally + { + loading = false; + } + } + + #region Database Credential Methods + + private void ShowAddDatabaseModal() + { + editingDatabaseCredential = null; + currentDatabaseCredential = new DatabaseCredential + { + DatabaseType = CredentialManager.Models.DatabaseType.SqlServer, + Port = 1433, + CommandTimeout = 30 + }; + showDatabaseModal = true; + } + + private void EditDatabaseCredential(DatabaseCredential credential) + { + editingDatabaseCredential = credential; + currentDatabaseCredential = new DatabaseCredential + { + Name = credential.Name, + DatabaseType = credential.DatabaseType, + Host = credential.Host, + Port = credential.Port, + DatabaseName = credential.DatabaseName, + Username = credential.Username, + Password = credential.Password, + CommandTimeout = credential.CommandTimeout, + IgnoreSslErrors = credential.IgnoreSslErrors + }; + showDatabaseModal = true; + } + + private async Task SaveDatabaseCredential() + { + try + { + await CredentialService.SaveDatabaseCredentialAsync(currentDatabaseCredential); + await JSRuntime.InvokeVoidAsync("alert", "Credenziale database salvata con successo!"); + CloseDatabaseModal(); + await RefreshCredentials(); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvare la credenziale: {ex.Message}"); + } + } + + private void CloseDatabaseModal() + { + showDatabaseModal = false; + editingDatabaseCredential = null; + } private async Task TestDatabaseConnection(DatabaseCredential credential) + { + try + { + var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); + + var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore"; + await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}"); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}"); + } + } + + private async Task TestCurrentDatabaseConnection() + { + if (testingConnection) return; + + testingConnection = true; + try + { + // Valida i campi obbligatori + if (string.IsNullOrEmpty(currentDatabaseCredential.Name) || + string.IsNullOrEmpty(currentDatabaseCredential.Host) || + string.IsNullOrEmpty(currentDatabaseCredential.Username) || + string.IsNullOrEmpty(currentDatabaseCredential.Password)) + { + await JSRuntime.InvokeVoidAsync("alert", "Compila tutti i campi obbligatori prima di testare la connessione."); + return; + } + + var (success, message) = await CredentialService.TestDatabaseConnectionAsync(currentDatabaseCredential); + + var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore"; + await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}"); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}"); + } + finally + { + testingConnection = false; + } + } + + #endregion + + #region REST API Credential Methods + + private void ShowAddRestApiModal() + { + editingRestApiCredential = null; + currentRestApiCredential = new RestApiCredential + { + ServiceType = RestServiceType.Generic, + TimeoutSeconds = 100 + }; + SetDefaultsForServiceType(currentRestApiCredential.ServiceType); + showRestApiModal = true; + } + + private void EditRestApiCredential(RestApiCredential credential) + { + editingRestApiCredential = credential; + currentRestApiCredential = new RestApiCredential + { + Name = credential.Name, + ServiceType = credential.ServiceType, + BaseUrl = credential.BaseUrl, + ApiKey = credential.ApiKey, + Username = credential.Username, + Password = credential.Password, + AuthToken = credential.AuthToken, + BearerToken = credential.BearerToken, + TimeoutSeconds = credential.TimeoutSeconds, + IgnoreSslErrors = credential.IgnoreSslErrors, + Headers = credential.Headers, + AdditionalParameters = credential.AdditionalParameters, + // Campi SAP B1 + CompanyDatabase = credential.CompanyDatabase, + Language = credential.Language, + Version = credential.Version, + UseTrustedConnection = credential.UseTrustedConnection, + // Campi Salesforce + SecurityToken = credential.SecurityToken, + ClientId = credential.ClientId, + ClientSecret = credential.ClientSecret, + ApiVersion = credential.ApiVersion, + IsSandbox = credential.IsSandbox, + UseSoapApi = credential.UseSoapApi, + RefreshToken = credential.RefreshToken, + AccessToken = credential.AccessToken, + TokenExpiry = credential.TokenExpiry + }; + showRestApiModal = true; + } + + private async Task SaveRestApiCredential() + { + try + { + await CredentialService.SaveRestApiCredentialAsync(currentRestApiCredential); + await JSRuntime.InvokeVoidAsync("alert", $"Credenziale {GetServiceTypeDisplayName(currentRestApiCredential.ServiceType)} salvata con successo!"); + CloseRestApiModal(); + await RefreshCredentials(); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvare la credenziale: {ex.Message}"); + } + } + + private void CloseRestApiModal() + { + showRestApiModal = false; + editingRestApiCredential = null; + } + + private async Task TestRestApiConnection(RestApiCredential credential) + { + try + { + var (success, message) = credential.ServiceType switch + { + RestServiceType.SapB1ServiceLayer => await CredentialService.TestSapB1ConnectionAsync(credential.Name), + RestServiceType.Salesforce => await CredentialService.TestSalesforceConnectionAsync(credential.Name), + _ => await CredentialService.TestRestApiConnectionAsync(credential.Name) + }; + + var title = success ? $"Test {GetServiceTypeDisplayName(credential.ServiceType)} - Successo" : $"Test {GetServiceTypeDisplayName(credential.ServiceType)} - Errore"; + await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}"); + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}"); + } + } + + private void OnServiceTypeChanged(ChangeEventArgs e) + { + if (Enum.TryParse(e.Value?.ToString(), out var serviceType)) + { + currentRestApiCredential.ServiceType = serviceType; + SetDefaultsForServiceType(serviceType); + } + } + + private void SetDefaultsForServiceType(RestServiceType serviceType) + { + switch (serviceType) + { + case RestServiceType.SapB1ServiceLayer: + currentRestApiCredential.Language = "en-US"; + currentRestApiCredential.Version = "v1"; + currentRestApiCredential.TimeoutSeconds = 300; + if (string.IsNullOrEmpty(currentRestApiCredential.BaseUrl)) + currentRestApiCredential.BaseUrl = "https://server:50000/b1s/v1/"; + break; + case RestServiceType.Salesforce: + currentRestApiCredential.ApiVersion = "59.0"; + currentRestApiCredential.TimeoutSeconds = 120; + currentRestApiCredential.IsSandbox = false; + currentRestApiCredential.UseSoapApi = false; + if (string.IsNullOrEmpty(currentRestApiCredential.BaseUrl)) + currentRestApiCredential.BaseUrl = "https://login.salesforce.com"; + break; + case RestServiceType.Generic: + default: + currentRestApiCredential.TimeoutSeconds = 100; + break; + } + } + + private string GetServiceTypeDisplayName(RestServiceType serviceType) + { + return serviceType switch + { + RestServiceType.SapB1ServiceLayer => "SAP B1 Service Layer", + RestServiceType.Salesforce => "Salesforce", + RestServiceType.Generic => "REST API", + _ => "REST API" + }; + } + + private string GetUrlFieldLabel(RestServiceType serviceType) + { + return serviceType switch + { + RestServiceType.SapB1ServiceLayer => "Server URL", + RestServiceType.Salesforce => "Login URL", + _ => "Base URL" + }; + } + + private string GetUrlPlaceholder(RestServiceType serviceType) + { + return serviceType switch + { + RestServiceType.SapB1ServiceLayer => "https://server:50000/b1s/v1/", + RestServiceType.Salesforce => "https://login.salesforce.com", + _ => "https://api.example.com" + }; + } + + private string GetUrlHelpText(RestServiceType serviceType) + { + return serviceType switch + { + RestServiceType.SapB1ServiceLayer => "URL del SAP B1 Service Layer (esempio: https://server:50000/b1s/v1/)", + RestServiceType.Salesforce => "Production: https://login.salesforce.com | Sandbox: https://test.salesforce.com", + _ => "URL base del servizio REST API" + }; + } private void OnSandboxChanged(ChangeEventArgs e) + { + if (bool.TryParse(e.Value?.ToString(), out bool isSandbox)) + { + currentRestApiCredential.IsSandbox = isSandbox; + currentRestApiCredential.BaseUrl = isSandbox + ? "https://test.salesforce.com" + : "https://login.salesforce.com"; + } } + + #endregion + + #region Common Methods + + private async Task DeleteCredential(string name, bool isDatabase) + { + if (await JSRuntime.InvokeAsync("confirm", $"Sei sicuro di voler eliminare la credenziale '{name}'?")) + { + try + { + bool success; + if (isDatabase) + success = await CredentialService.DeleteDatabaseCredentialAsync(name); + else + success = await CredentialService.DeleteRestApiCredentialAsync(name); + + if (success) + { + await JSRuntime.InvokeVoidAsync("alert", "Credenziale eliminata con successo!"); + await RefreshCredentials(); + } + else + { + await JSRuntime.InvokeVoidAsync("alert", "Errore nell'eliminazione della credenziale."); + } + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nell'eliminazione: {ex.Message}"); + } + } + } + + private string GetAuthenticationType(RestApiCredential credential) + { + if (!string.IsNullOrEmpty(credential.ApiKey)) + return "API Key"; + if (!string.IsNullOrEmpty(credential.AuthToken)) + return "Auth Token"; + if (!string.IsNullOrEmpty(credential.Username)) + return "Basic Auth"; + return "Nessuna"; + } + + private async Task TestRestApiConnectionFromModal() + { + try + { + testingConnection = true; + + // Creiamo una credenziale temporanea per il test + var tempCredential = new RestApiCredential + { + Name = $"temp_test_{Guid.NewGuid():N}", + ServiceType = currentRestApiCredential.ServiceType, + BaseUrl = currentRestApiCredential.BaseUrl, + Username = currentRestApiCredential.Username, + Password = currentRestApiCredential.Password, + ApiKey = currentRestApiCredential.ApiKey, + AuthToken = currentRestApiCredential.AuthToken, + TimeoutSeconds = currentRestApiCredential.TimeoutSeconds, + IgnoreSslErrors = currentRestApiCredential.IgnoreSslErrors, + // Campi SAP B1 + CompanyDatabase = currentRestApiCredential.CompanyDatabase, + Version = currentRestApiCredential.Version, + Language = currentRestApiCredential.Language, + UseTrustedConnection = currentRestApiCredential.UseTrustedConnection, + // Campi Salesforce + SecurityToken = currentRestApiCredential.SecurityToken, + ClientId = currentRestApiCredential.ClientId, + ClientSecret = currentRestApiCredential.ClientSecret, + ApiVersion = currentRestApiCredential.ApiVersion, + IsSandbox = currentRestApiCredential.IsSandbox, + UseSoapApi = currentRestApiCredential.UseSoapApi + }; // Salviamo temporaneamente la credenziale per il test + await CredentialService.SaveRestApiCredentialAsync(tempCredential); + + // Testiamo la connessione + var (success, message) = await CredentialService.TestRestApiConnectionAsync(tempCredential.Name); + + // Rimuoviamo la credenziale temporanea + await CredentialService.DeleteRestApiCredentialAsync(tempCredential.Name); + + var title = success ? "Test Connessione - Successo" : "Test Connessione - Errore"; + await JSRuntime.InvokeVoidAsync("alert", $"{title}\n\n{message}"); + + } + catch (Exception ex) + { + await JSRuntime.InvokeVoidAsync("alert", $"Errore nel test della connessione: {ex.Message}"); + } + finally + { + testingConnection = false; + } + } + + private bool IsFieldRequired(string fieldName, RestServiceType serviceType) + { + return (fieldName, serviceType) switch + { + ("Username", RestServiceType.Generic) => string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken), + ("Password", RestServiceType.Generic) => string.IsNullOrEmpty(currentRestApiCredential.ApiKey) && string.IsNullOrEmpty(currentRestApiCredential.AuthToken), + ("Username", RestServiceType.SapB1ServiceLayer) => !currentRestApiCredential.UseTrustedConnection, + ("Password", RestServiceType.SapB1ServiceLayer) => !currentRestApiCredential.UseTrustedConnection, + ("CompanyDatabase", RestServiceType.SapB1ServiceLayer) => true, + ("Username", RestServiceType.Salesforce) => true, + ("Password", RestServiceType.Salesforce) => true, + ("SecurityToken", RestServiceType.Salesforce) => string.IsNullOrEmpty(currentRestApiCredential.ClientId) && string.IsNullOrEmpty(currentRestApiCredential.ClientSecret), + _ => false + }; + } + + private string GetFieldLabel(string fieldName, RestServiceType serviceType) + { + var label = (fieldName, serviceType) switch + { + ("Username", RestServiceType.SapB1ServiceLayer) => "Username", + ("Password", RestServiceType.SapB1ServiceLayer) => "Password", + ("CompanyDatabase", RestServiceType.SapB1ServiceLayer) => "Company Database", + ("Username", RestServiceType.Salesforce) => "Username", + ("Password", RestServiceType.Salesforce) => "Password", + ("SecurityToken", RestServiceType.Salesforce) => "Security Token", + _ => fieldName + }; + + return IsFieldRequired(fieldName, serviceType) ? $"{label} *" : label; + } + + #endregion +} diff --git a/Data_Coupler/Program.cs b/Data_Coupler/Program.cs index 45961b8..1e47f71 100644 --- a/Data_Coupler/Program.cs +++ b/Data_Coupler/Program.cs @@ -5,6 +5,8 @@ using DataConnection.EF; using DataConnection.Interfaces; using DataConnection.EF.DatabaseDiscovery; using DataConnection.Enums; +using DataConnection.CredentialManagement; +using CredentialManager; using System; using System.Threading.Tasks; @@ -14,11 +16,35 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); +// Add CredentialManager services +var contentRoot = builder.Environment.ContentRootPath; +var dataPath = Path.Combine(contentRoot, "wwwroot", "data"); +Directory.CreateDirectory(dataPath); // Assicurati che la directory esista +var dbPath = Path.Combine(dataPath, "credentials.db"); +builder.Services.AddDataConnectionCredentialManagement($"Data Source={dbPath}"); + // Register IHttpClientFactory builder.Services.AddHttpClient(); var app = builder.Build(); +// Initialize database +using (var scope = app.Services.CreateScope()) +{ + var logger = scope.ServiceProvider.GetRequiredService>(); + try + { + logger.LogInformation("Inizializzazione database in corso..."); + var dbInitializer = scope.ServiceProvider.GetRequiredService(); + dbInitializer.InitializeAsync().GetAwaiter().GetResult(); + logger.LogInformation("Database inizializzato con successo."); + } + catch (Exception ex) + { + logger.LogError(ex, "Errore durante l'inizializzazione del database: {Message}", ex.Message); + throw; // Rilancia l'eccezione per non far partire l'app con un database non funzionante + } +} // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) diff --git a/Data_Coupler/Shared/NavMenu.razor b/Data_Coupler/Shared/NavMenu.razor index 02aeaed..04da039 100644 --- a/Data_Coupler/Shared/NavMenu.razor +++ b/Data_Coupler/Shared/NavMenu.razor @@ -18,25 +18,14 @@ Counter - - - - diff --git a/Data_Coupler/wwwroot/data/credentials.db b/Data_Coupler/wwwroot/data/credentials.db new file mode 100644 index 0000000..8b37ffd Binary files /dev/null and b/Data_Coupler/wwwroot/data/credentials.db differ diff --git a/Data_Coupler/wwwroot/data/credentials.db-shm b/Data_Coupler/wwwroot/data/credentials.db-shm new file mode 100644 index 0000000..3ffe6e3 Binary files /dev/null and b/Data_Coupler/wwwroot/data/credentials.db-shm differ diff --git a/Data_Coupler/wwwroot/data/credentials.db-wal b/Data_Coupler/wwwroot/data/credentials.db-wal new file mode 100644 index 0000000..73e9b15 Binary files /dev/null and b/Data_Coupler/wwwroot/data/credentials.db-wal differ diff --git a/README.md b/README.md index e110d65..967085a 100644 --- a/README.md +++ b/README.md @@ -1 +1,167 @@ -# Data-Coupler \ No newline at end of file +# Data-Coupler + +## Panoramica + +Data-Coupler è una soluzione integrata per la gestione di connessioni dati e credenziali, composta da tre progetti principali: + +- **CredentialManager**: Libreria per la gestione sicura delle credenziali +- **DataConnection**: Libreria per connessioni a database e API REST +- **Data_Coupler**: Applicazione Blazor Server per l'interfaccia utente + +## Architettura + +### CredentialManager +Libreria responsabile per: +- Gestione sicura delle credenziali (Database, REST API) +- Crittografia dei dati sensibili +- Persistenza su database SQLite +- Validazione delle credenziali + +### DataConnection +Libreria per connessioni dati che include: +- Supporto per database: SQL Server, MySQL, PostgreSQL, Oracle, SQLite, DB2, SAP HANA +- Gestione connessioni REST API +- Integrazione con CredentialManager per l'autenticazione +- Factory pattern per la gestione di diversi provider + +### Data_Coupler +Applicazione Blazor Server che fornisce: +- Interfaccia web per la gestione delle credenziali +- CRUD completo per credenziali database e REST API +- Test delle connessioni +- Validazione form integrata + +## Configurazione + +### 1. Database delle Credenziali + +L'applicazione utilizza SQLite per memorizzare le credenziali. Il database viene automaticamente creato in: +``` +Data_Coupler/wwwroot/data/credentials.db +``` + +### 2. Registrazione Servizi + +Nel `Program.cs` di Data_Coupler: + +```csharp +// Add CredentialManager services +builder.Services.AddDataConnectionCredentialManagement("Data Source=wwwroot/data/credentials.db"); +``` + +## Utilizzo + +### Gestione Credenziali via Web Interface + +1. Avviare l'applicazione Data_Coupler +2. Navigare su `/credentials` +3. Utilizzare l'interfaccia per: + - Aggiungere nuove credenziali (Database/REST API) + - Modificare credenziali esistenti + - Testare le connessioni + - Eliminare credenziali + +### Utilizzo Programmatico + +```csharp +// Iniettare il servizio +@inject IDataConnectionCredentialService CredentialService + +// Ottenere opzioni per connessione database +var dbOptions = await CredentialService.GetDbManagerOptionsAsync("MyDatabase"); + +// Ottenere opzioni per API REST +var apiOptions = await CredentialService.GetRestServiceOptionsAsync("MyApi"); + +// Gestire credenziali +var credential = new DatabaseCredential +{ + Name = "Test", + DatabaseType = DatabaseType.SqlServer, + Host = "localhost", + Port = 1433, + DatabaseName = "TestDB", + Username = "user", + Password = "password" +}; + +await CredentialService.SaveDatabaseCredentialAsync(credential); +``` + +## Struttura dei Progetti + +``` +Data-Coupler/ +├── CredentialManager/ # Gestione credenziali +│ ├── Data/ # DbContext e configurazioni +│ ├── Models/ # Modelli per credenziali +│ └── Services/ # Servizi core +├── DataConnection/ # Connessioni dati +│ ├── CredentialManagement/ # Integrazione con CredentialManager +│ │ ├── Interfaces/ # Interfacce servizi +│ │ ├── Models/ # Extension methods e conversioni +│ │ └── Services/ # Implementazione servizi +│ ├── DB/ # Gestione database +│ └── REST/ # Gestione API REST +└── Data_Coupler/ # Applicazione Blazor + ├── Pages/ # Pagine Blazor + │ └── CredentialManagement.razor + ├── Shared/ # Componenti condivisi + └── wwwroot/data/ # Database SQLite +``` + +## Build e Deployment + +### Prerequisiti +- .NET 9.0 SDK +- Visual Studio 2022 o VS Code + +### Build +```bash +dotnet build Data_Coupler.sln +``` + +### Esecuzione +```bash +dotnet run --project Data_Coupler/Data_Coupler.csproj +``` + +L'applicazione sarà disponibile su: +- HTTP: http://localhost:5135 +- HTTPS: https://localhost:7132 + +## Caratteristiche di Sicurezza + +- **Crittografia**: Le password vengono crittografate prima del salvataggio +- **Validazione**: Validazione completa dei dati in input +- **Isolamento**: Ogni progetto ha responsabilità specifiche +- **Type Safety**: Uso di tipi forti per evitare errori + +## Testing + +L'applicazione include funzionalità di test per: +- Connessioni database +- Chiamate API REST +- Validazione credenziali + +Il testing può essere eseguito direttamente dall'interfaccia web. + +## Log e Monitoring + +I servizi utilizzano ILogger per tracciare: +- Operazioni CRUD sulle credenziali +- Test di connessione +- Errori e eccezioni + +## Contributi + +Per contribuire al progetto: +1. Fork del repository +2. Creare un branch per la feature +3. Implementare i cambiamenti +4. Testare thoroughly +5. Creare una Pull Request + +## Licenza + +[Specificare la licenza del progetto] \ No newline at end of file