feat: implementa cache intelligente per credenziali REST con hash SHA256
- Aggiunge classe CachedRestClient per tracciare client con hash delle credenziali - Implementa verifica automatica delle credenziali dal database tramite hash SHA256 - Mantiene cache dei client REST autenticati preservando lo stato di autenticazione - Invalida automaticamente la cache quando le credenziali cambiano nel database - Aggiunge logging dettagliato per debug dell'autenticazione Salesforce - Migliora mappatura credenziali Salesforce con validazione Password e ClientSecret BREAKING CHANGE: Le credenziali REST vengono sempre verificate dal database mantenendo performance attraverso cache intelligente basata su hash crittografico Fixes: risolve problemi di "invalid_client_id" intermittenti e _accessToken null
This commit is contained in:
@@ -132,13 +132,13 @@ namespace DataConnection.REST.Implementations
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password))
|
if (string.IsNullOrEmpty(_options.Username) || string.IsNullOrEmpty(_options.Password))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Salesforce authentication requires username and password in options");
|
Console.WriteLine($"Salesforce authentication requires username and password in options. Username: '{_options.Username}', Password: '{(!string.IsNullOrEmpty(_options.Password) ? "***SET***" : "***NULL***")}'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_options.ApiKey) || string.IsNullOrEmpty(_options.AuthToken))
|
if (string.IsNullOrEmpty(_options.ApiKey) || string.IsNullOrEmpty(_options.AuthToken))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Salesforce authentication requires ApiKey (ClientId) and AuthToken (ClientSecret) in options");
|
Console.WriteLine($"Salesforce authentication requires ApiKey (ClientId) and AuthToken (ClientSecret) in options. ApiKey: '{_options.ApiKey}', AuthToken: '{(!string.IsNullOrEmpty(_options.AuthToken) ? "***SET***" : "***NULL***")}'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,21 @@ using DataConnection.REST.Configuration;
|
|||||||
using DataConnection.CredentialManagement.Interfaces;
|
using DataConnection.CredentialManagement.Interfaces;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Data_Coupler.Services
|
namespace Data_Coupler.Services
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Classe per tracciare i client REST cached con le loro credenziali
|
||||||
|
/// </summary>
|
||||||
|
internal class CachedRestClient
|
||||||
|
{
|
||||||
|
public IRestServiceClient Client { get; set; } = null!;
|
||||||
|
public string CredentialsHash { get; set; } = string.Empty;
|
||||||
|
public DateTime CachedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Factory service per creare istanze di DatabaseManager e RestClient
|
/// Factory service per creare istanze di DatabaseManager e RestClient
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -46,8 +58,8 @@ namespace Data_Coupler.Services
|
|||||||
private readonly ILogger<DataConnectionFactory> _logger;
|
private readonly ILogger<DataConnectionFactory> _logger;
|
||||||
private readonly IDataConnectionCredentialService _credentialService;
|
private readonly IDataConnectionCredentialService _credentialService;
|
||||||
|
|
||||||
// Cache per mantenere le istanze dei client REST autenticati
|
// Cache intelligente per client REST che mantiene lo stato di autenticazione ma verifica le credenziali
|
||||||
private readonly Dictionary<string, IRestServiceClient> _restClientCache = new();
|
private readonly Dictionary<string, CachedRestClient> _restClientCache = new();
|
||||||
|
|
||||||
public DataConnectionFactory(IServiceProvider serviceProvider, ILogger<DataConnectionFactory> logger, IDataConnectionCredentialService credentialService)
|
public DataConnectionFactory(IServiceProvider serviceProvider, ILogger<DataConnectionFactory> logger, IDataConnectionCredentialService credentialService)
|
||||||
{
|
{
|
||||||
@@ -76,17 +88,29 @@ namespace Data_Coupler.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Controlla se abbiamo già un client cached per questa credenziale
|
// SEMPRE recupera le credenziali fresh dal database per assicurare che siano aggiornate
|
||||||
if (_restClientCache.TryGetValue(credentialName, out var cachedClient))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Utilizzando client REST cached per {CredentialName}", credentialName);
|
|
||||||
return cachedClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
|
var credential = await _credentialService.GetRestApiCredentialAsync(credentialName);
|
||||||
if (credential == null)
|
if (credential == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Credenziale REST API '{credentialName}' non trovata");
|
throw new ArgumentException($"Credenziale REST API '{credentialName}' non trovata");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcola l'hash delle credenziali correnti
|
||||||
|
var currentCredentialsHash = CalculateCredentialsHash(credential);
|
||||||
|
|
||||||
|
// Controlla se abbiamo un client cached con le stesse credenziali
|
||||||
|
if (_restClientCache.TryGetValue(credentialName, out var cachedClient))
|
||||||
|
{
|
||||||
|
if (cachedClient.CredentialsHash == currentCredentialsHash)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Utilizzando client REST cached con credenziali verificate per {CredentialName}", credentialName);
|
||||||
|
return cachedClient.Client;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Credenziali cambiate, rimuovendo client cached per {CredentialName}", credentialName);
|
||||||
|
_restClientCache.Remove(credentialName);
|
||||||
|
}
|
||||||
} var options = new RestServiceOptions
|
} var options = new RestServiceOptions
|
||||||
{
|
{
|
||||||
BaseUrl = credential.BaseUrl,
|
BaseUrl = credential.BaseUrl,
|
||||||
@@ -101,8 +125,11 @@ namespace Data_Coupler.Services
|
|||||||
// Per Salesforce usiamo i campi specifici ClientId e ClientSecret
|
// Per Salesforce usiamo i campi specifici ClientId e ClientSecret
|
||||||
options.ApiKey = credential.ClientId; // ClientId -> ApiKey
|
options.ApiKey = credential.ClientId; // ClientId -> ApiKey
|
||||||
options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken
|
options.AuthToken = credential.ClientSecret; // ClientSecret -> AuthToken
|
||||||
_logger.LogInformation("Salesforce mapping - ClientId: {ClientId}, ClientSecret: {HasSecret}, Username: {Username}",
|
_logger.LogInformation("Salesforce mapping - ClientId: '{ClientId}', ClientSecret: {HasSecret}, Username: '{Username}', Password: {HasPassword}",
|
||||||
credential.ClientId, !string.IsNullOrEmpty(credential.ClientSecret), credential.Username);
|
credential.ClientId,
|
||||||
|
!string.IsNullOrEmpty(credential.ClientSecret),
|
||||||
|
credential.Username,
|
||||||
|
!string.IsNullOrEmpty(credential.Password));
|
||||||
break;
|
break;
|
||||||
case RestServiceType.SapB1ServiceLayer:
|
case RestServiceType.SapB1ServiceLayer:
|
||||||
// Per SAP B1 usiamo il CompanyDatabase come ApiKey
|
// Per SAP B1 usiamo il CompanyDatabase come ApiKey
|
||||||
@@ -128,9 +155,15 @@ namespace Data_Coupler.Services
|
|||||||
_ => throw new NotSupportedException($"Tipo di servizio REST non supportato: {credential.ServiceType}")
|
_ => throw new NotSupportedException($"Tipo di servizio REST non supportato: {credential.ServiceType}")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Salva il client nella cache
|
// Salva il client nella cache con l'hash delle credenziali
|
||||||
_restClientCache[credentialName] = client;
|
_restClientCache[credentialName] = new CachedRestClient
|
||||||
_logger.LogInformation("Client REST creato e cached per {CredentialName}", credentialName);
|
{
|
||||||
|
Client = client,
|
||||||
|
CredentialsHash = currentCredentialsHash,
|
||||||
|
CachedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogInformation("Client REST creato e cached con credenziali aggiornate per {CredentialName}", credentialName);
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
@@ -143,13 +176,13 @@ namespace Data_Coupler.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Utilizza lo stesso client REST cached (che è già autenticato)
|
// Crea un nuovo client REST con credenziali aggiornate
|
||||||
var restClient = await CreateRestServiceClientAsync(credentialName);
|
var restClient = await CreateRestServiceClientAsync(credentialName);
|
||||||
|
|
||||||
// I service client già implementano IRestMetadataDiscovery
|
// I service client già implementano IRestMetadataDiscovery
|
||||||
if (restClient is IRestMetadataDiscovery metadataDiscovery)
|
if (restClient is IRestMetadataDiscovery metadataDiscovery)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Utilizzando lo stesso client REST per metadata discovery per {CredentialName}", credentialName);
|
_logger.LogInformation("Utilizzando client REST per metadata discovery con credenziali aggiornate per {CredentialName}", credentialName);
|
||||||
return metadataDiscovery;
|
return metadataDiscovery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +211,17 @@ namespace Data_Coupler.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pulisce la cache del client REST per una credenziale specifica
|
/// Calcola un hash delle credenziali per rilevare cambiamenti
|
||||||
|
/// </summary>
|
||||||
|
private string CalculateCredentialsHash(RestApiCredential credential)
|
||||||
|
{
|
||||||
|
var credentialString = $"{credential.BaseUrl}|{credential.Username}|{credential.Password}|{credential.ApiKey}|{credential.AuthToken}|{credential.ClientId}|{credential.ClientSecret}|{credential.CompanyDatabase}";
|
||||||
|
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(credentialString));
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulisce la cache del client REST per una credenziale specifica.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearRestClientCache(string credentialName)
|
public void ClearRestClientCache(string credentialName)
|
||||||
{
|
{
|
||||||
@@ -189,7 +232,7 @@ namespace Data_Coupler.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Pulisce tutta la cache dei client REST
|
/// Pulisce tutta la cache dei client REST.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void ClearAllRestClientCache()
|
public void ClearAllRestClientCache()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user