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:
Vendored
+32
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -4,6 +4,17 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<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>
|
</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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -64,9 +64,7 @@ public class DbManagerOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tipo di database (SqlServer, MySql, ecc.)
|
/// Tipo di database (SqlServer, MySql, ecc.)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer;
|
public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer; /// <summary>
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configura automaticamente il servizio di scoperta database in base al tipo di database
|
/// Configura automaticamente il servizio di scoperta database in base al tipo di database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="databaseType">Tipo di database</param>
|
/// <param name="databaseType">Tipo di database</param>
|
||||||
@@ -78,13 +76,14 @@ public class DbManagerOptions
|
|||||||
{
|
{
|
||||||
case DatabaseType.SqlServer:
|
case DatabaseType.SqlServer:
|
||||||
DatabaseDiscoveryService = new SqlServerDatabaseDiscovery();
|
DatabaseDiscoveryService = new SqlServerDatabaseDiscovery();
|
||||||
|
DbContextConfigurator = options => options.UseSqlServer(BuildFullConnectionString(),
|
||||||
|
sqlOptions => sqlOptions.CommandTimeout(CommandTimeout));
|
||||||
break;
|
break;
|
||||||
// case DatabaseType.MySql:
|
|
||||||
// DatabaseDiscoveryService = new MySqlDatabaseDiscovery();
|
|
||||||
// break;
|
|
||||||
// Altri tipi di database possono essere aggiunti qui
|
|
||||||
default:
|
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,13 +8,18 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="DB\Models\" />
|
<Folder Include="DB\Models\" />
|
||||||
</ItemGroup>
|
</ItemGroup> <ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="IBM.EntityFrameworkCore" Version="6.0.0.300" />
|
<PackageReference Include="IBM.EntityFrameworkCore" Version="6.0.0.300" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" 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" />
|
<PackageReference Include="System.Data.Odbc" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CredentialManager\CredentialManager.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ using DataConnection.EF;
|
|||||||
using DataConnection.Interfaces;
|
using DataConnection.Interfaces;
|
||||||
using DataConnection.EF.DatabaseDiscovery;
|
using DataConnection.EF.DatabaseDiscovery;
|
||||||
using DataConnection.Enums;
|
using DataConnection.Enums;
|
||||||
|
using DataConnection.CredentialManagement;
|
||||||
|
using CredentialManager;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -14,11 +16,35 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
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
|
// Register IHttpClientFactory
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
var app = builder.Build();
|
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.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
|
|||||||
@@ -18,25 +18,14 @@
|
|||||||
<NavLink class="nav-link" href="counter">
|
<NavLink class="nav-link" href="counter">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
<span class="oi oi-plus" aria-hidden="true"></span> Counter
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div> <div class="nav-item px-3">
|
||||||
<div class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="fetchdata">
|
<NavLink class="nav-link" href="fetchdata">
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item px-3">
|
<div class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="database-connection">
|
<NavLink class="nav-link" href="credentials">
|
||||||
<span class="oi oi-hard-drive" aria-hidden="true"></span> Database
|
<span class="oi oi-key" aria-hidden="true"></span> Gestione Credenziali
|
||||||
</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>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1,167 @@
|
|||||||
# Data-Coupler
|
# 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]
|
||||||
Reference in New Issue
Block a user