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

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

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

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

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

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

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

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

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

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