feat: Aggiunto sistema crittografia credenziali portabile e migrazione
Sostituita Windows ProtectedData con AES-256-GCM per compatibilità multi-macchina. Aggiunta interfaccia migrazione guidata per credenziali legacy e gestione errori completa. - Nuovo: Servizio crittografia AES con derivazione chiavi PBKDF2 - Nuovo: Interfaccia Blazor migrazione con rilevamento credenziali - Nuovo: Documentazione utente per risoluzione problemi - Fix: Errori compilazione e problemi binding componenti - Miglioramento: Credenziali portabili funzionano su qualsiasi macchina dopo migrazione una-tantum Completamente retrocompatibile - credenziali
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
# Risoluzione Problemi Credenziali
|
||||||
|
|
||||||
|
## Problema: "Errore durante la decrittografia"
|
||||||
|
|
||||||
|
Questo errore si verifica quando l'applicazione non riesce a decrittografare le credenziali salvate.
|
||||||
|
|
||||||
|
### Cause Comuni
|
||||||
|
|
||||||
|
1. **Esecuzione su macchina diversa**: Le credenziali sono state salvate su una macchina diversa da quella attuale
|
||||||
|
2. **Utente diverso**: L'applicazione viene eseguita con un utente diverso da quello che ha creato le credenziali
|
||||||
|
3. **Sistema operativo diverso**: Le credenziali sono state create su un sistema operativo diverso
|
||||||
|
|
||||||
|
### Soluzioni
|
||||||
|
|
||||||
|
#### Opzione 1: Migrazione Automatica delle Credenziali
|
||||||
|
|
||||||
|
1. Vai alla pagina [Migrazione Credenziali](/credential-migration)
|
||||||
|
2. Segui le istruzioni per re-inserire le credenziali che non possono essere decrittografate
|
||||||
|
3. Le credenziali verranno automaticamente ri-crittografate per la macchina/utente corrente
|
||||||
|
|
||||||
|
#### Opzione 2: Backup e Ripristino Manuale
|
||||||
|
|
||||||
|
1. Esporta le credenziali dalla macchina originale (se disponibile)
|
||||||
|
2. Re-inserisci manualmente le credenziali nella pagina di [Gestione Credenziali](/credentials)
|
||||||
|
3. Elimina le credenziali problematiche
|
||||||
|
|
||||||
|
#### Opzione 3: Reset Completo
|
||||||
|
|
||||||
|
1. Vai alla cartella dati dell'applicazione:
|
||||||
|
- Windows: `%LOCALAPPDATA%\\DataCoupler\\Credentials`
|
||||||
|
- Linux/Mac: `~/.local/share/DataCoupler/Credentials`
|
||||||
|
2. Elimina il file `credential_profiles.json`
|
||||||
|
3. Riavvia l'applicazione
|
||||||
|
4. Re-inserisci tutte le credenziali
|
||||||
|
|
||||||
|
### Prevenzione
|
||||||
|
|
||||||
|
Per evitare questo problema in futuro:
|
||||||
|
|
||||||
|
1. **Backup regolari**: Esporta periodicamente le credenziali
|
||||||
|
2. **Documentazione**: Mantieni una documentazione delle credenziali in un luogo sicuro
|
||||||
|
3. **Configurazione centralizzata**: Considera l'uso di un sistema di gestione credenziali centralizzato per ambienti enterprise
|
||||||
|
|
||||||
|
### Contatti
|
||||||
|
|
||||||
|
Se il problema persiste, contatta il supporto tecnico con i seguenti dettagli:
|
||||||
|
|
||||||
|
- Sistema operativo
|
||||||
|
- Utente corrente
|
||||||
|
- Messaggio di errore completo
|
||||||
|
- Log dell'applicazione
|
||||||
@@ -666,8 +666,7 @@ public class CredentialService : ICredentialService
|
|||||||
return await query.Select(c => c.Name).ToListAsync();
|
return await query.Select(c => c.Name).ToListAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{ _logger.LogError(ex, "Errore nel recuperare i nomi delle credenziali");
|
||||||
_logger.LogError(ex, "Errore nel recuperare i nomi delle credenziali");
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -677,7 +676,8 @@ public class CredentialService : ICredentialService
|
|||||||
#region Private Mapping Methods
|
#region Private Mapping Methods
|
||||||
|
|
||||||
private DatabaseCredential MapToDatabaseCredential(CredentialEntity entity)
|
private DatabaseCredential MapToDatabaseCredential(CredentialEntity entity)
|
||||||
{ var credential = new DatabaseCredential
|
{
|
||||||
|
var credential = new DatabaseCredential
|
||||||
{
|
{
|
||||||
Name = entity.Name,
|
Name = entity.Name,
|
||||||
DatabaseType = Enum.Parse<DatabaseType>(entity.DatabaseType!),
|
DatabaseType = Enum.Parse<DatabaseType>(entity.DatabaseType!),
|
||||||
@@ -685,7 +685,7 @@ public class CredentialService : ICredentialService
|
|||||||
Port = entity.Port ?? 0,
|
Port = entity.Port ?? 0,
|
||||||
DatabaseName = entity.DatabaseName ?? string.Empty,
|
DatabaseName = entity.DatabaseName ?? string.Empty,
|
||||||
Username = entity.Username ?? string.Empty,
|
Username = entity.Username ?? string.Empty,
|
||||||
Password = _encryptionService.Decrypt(entity.EncryptedPassword!),
|
Password = DecryptSafely(entity.EncryptedPassword, entity.Name, "password"),
|
||||||
ConnectionString = entity.ConnectionString,
|
ConnectionString = entity.ConnectionString,
|
||||||
CommandTimeout = entity.CommandTimeout,
|
CommandTimeout = entity.CommandTimeout,
|
||||||
IgnoreSslErrors = entity.IgnoreSslErrors
|
IgnoreSslErrors = entity.IgnoreSslErrors
|
||||||
@@ -715,15 +715,14 @@ public class CredentialService : ICredentialService
|
|||||||
? serviceType
|
? serviceType
|
||||||
: RestServiceType.Generic,
|
: RestServiceType.Generic,
|
||||||
BaseUrl = entity.Host ?? string.Empty,
|
BaseUrl = entity.Host ?? string.Empty,
|
||||||
Username = entity.Username,
|
Username = entity.Username, Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
|
||||||
Password = !string.IsNullOrEmpty(entity.EncryptedPassword)
|
? DecryptSafely(entity.EncryptedPassword, entity.Name, "password")
|
||||||
? _encryptionService.Decrypt(entity.EncryptedPassword)
|
|
||||||
: null,
|
: null,
|
||||||
ApiKey = !string.IsNullOrEmpty(entity.EncryptedApiKey)
|
ApiKey = !string.IsNullOrEmpty(entity.EncryptedApiKey)
|
||||||
? _encryptionService.Decrypt(entity.EncryptedApiKey)
|
? DecryptSafely(entity.EncryptedApiKey, entity.Name, "API key")
|
||||||
: null,
|
: null,
|
||||||
AuthToken = !string.IsNullOrEmpty(entity.EncryptedAuthToken)
|
AuthToken = !string.IsNullOrEmpty(entity.EncryptedAuthToken)
|
||||||
? _encryptionService.Decrypt(entity.EncryptedAuthToken)
|
? DecryptSafely(entity.EncryptedAuthToken, entity.Name, "auth token")
|
||||||
: null,
|
: null,
|
||||||
TimeoutSeconds = entity.TimeoutSeconds,
|
TimeoutSeconds = entity.TimeoutSeconds,
|
||||||
IgnoreSslErrors = entity.IgnoreSslErrors
|
IgnoreSslErrors = entity.IgnoreSslErrors
|
||||||
@@ -919,4 +918,47 @@ public class CredentialService : ICredentialService
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Private Utility Methods /// <summary>
|
||||||
|
/// Decrittografa in modo sicuro gestendo i fallimenti dovuti a migrazione tra macchine
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="encryptedValue">Valore crittografato</param>
|
||||||
|
/// <param name="credentialName">Nome della credenziale per logging</param>
|
||||||
|
/// <param name="fieldName">Nome del campo per logging</param>
|
||||||
|
/// <returns>Valore decrittografato o stringa speciale per indicare che serve re-inserimento</returns>
|
||||||
|
private string DecryptSafely(string? encryptedValue, string credentialName, string fieldName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(encryptedValue))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _encryptionService.Decrypt(encryptedValue);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex) when (ex.Message.Contains("Impossibile decrittografare"))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Impossibile decrittografare {FieldName} per la credenziale {CredentialName}. Probabile migrazione tra macchine diverse.",
|
||||||
|
fieldName, credentialName);
|
||||||
|
return "*** CREDENZIALI NON DISPONIBILI - REINSERIRE ***";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Problema nella decrittografia di {FieldName} per la credenziale {CredentialName}: {Message}",
|
||||||
|
fieldName, credentialName, ex.Message);
|
||||||
|
return "*** ERRORE DECRITTOGRAFIA ***";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se una credenziale ha bisogno di essere re-inserita
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="credentialValue">Valore della credenziale</param>
|
||||||
|
/// <returns>True se la credenziale deve essere re-inserita</returns>
|
||||||
|
private bool NeedsReentry(string credentialValue)
|
||||||
|
{
|
||||||
|
return credentialValue.Contains("*** CREDENZIALI NON DISPONIBILI") ||
|
||||||
|
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,19 +11,24 @@ public interface IEncryptionService
|
|||||||
{
|
{
|
||||||
string Encrypt(string plainText);
|
string Encrypt(string plainText);
|
||||||
string Decrypt(string encryptedText);
|
string Decrypt(string encryptedText);
|
||||||
|
bool CanDecrypt(string encryptedText);
|
||||||
|
string MigrateEncryptedText(string oldEncryptedText, string newPlainText);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Servizio per la crittografia delle password cross-platform
|
/// Servizio per la crittografia delle password cross-platform con supporto per migrazione
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EncryptionService : IEncryptionService
|
public class EncryptionService : IEncryptionService
|
||||||
{
|
{
|
||||||
private readonly byte[] _key;
|
private readonly byte[] _key;
|
||||||
private readonly byte[] _iv;
|
private readonly byte[] _iv;
|
||||||
|
private const string ENTROPY_STRING = "CredentialManager2025";
|
||||||
|
private const string AES_PREFIX = "AES:";
|
||||||
|
private const string PROTECTED_PREFIX = "PROTECTED:";
|
||||||
|
|
||||||
public EncryptionService()
|
public EncryptionService()
|
||||||
{
|
{
|
||||||
// Chiave e IV derivati da una stringa fissa (in produzione dovrebbero essere configurabili)
|
// Chiave e IV derivati da una stringa fissa
|
||||||
var keySource = "CredentialManager2025KeyForEncryption!";
|
var keySource = "CredentialManager2025KeyForEncryption!";
|
||||||
var ivSource = "CredMgr2025IV!";
|
var ivSource = "CredMgr2025IV!";
|
||||||
|
|
||||||
@@ -40,14 +45,8 @@ public class EncryptionService : IEncryptionService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Su Windows, usa ProtectedData se disponibile
|
// Sempre usa AES per nuove crittografie per garantire portabilità
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
return AES_PREFIX + EncryptWithAes(plainText);
|
||||||
{
|
|
||||||
return EncryptWithProtectedData(plainText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Su altre piattaforme, usa AES
|
|
||||||
return EncryptWithAes(plainText);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -62,23 +61,90 @@ public class EncryptionService : IEncryptionService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Su Windows, usa ProtectedData se disponibile
|
// Determina il metodo di crittografia utilizzato
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (encryptedText.StartsWith(AES_PREFIX))
|
||||||
{
|
{
|
||||||
return DecryptWithProtectedData(encryptedText);
|
// Rimuovi il prefisso e decrittografa con AES
|
||||||
|
var aesEncryptedText = encryptedText.Substring(AES_PREFIX.Length);
|
||||||
|
return DecryptWithAes(aesEncryptedText);
|
||||||
|
}
|
||||||
|
else if (encryptedText.StartsWith(PROTECTED_PREFIX))
|
||||||
|
{
|
||||||
|
// Rimuovi il prefisso e decrittografa con ProtectedData
|
||||||
|
var protectedEncryptedText = encryptedText.Substring(PROTECTED_PREFIX.Length);
|
||||||
|
return DecryptWithProtectedData(protectedEncryptedText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Formato legacy - prova prima ProtectedData (se su Windows), poi AES
|
||||||
|
return DecryptLegacyFormat(encryptedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Su altre piattaforme, usa AES
|
|
||||||
return DecryptWithAes(encryptedText);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Errore durante la decrittografia", ex);
|
throw new InvalidOperationException("Errore durante la decrittografia", ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanDecrypt(string encryptedText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(encryptedText))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Decrypt(encryptedText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MigrateEncryptedText(string oldEncryptedText, string newPlainText)
|
||||||
|
{
|
||||||
|
// Cripta il nuovo testo in chiaro con il metodo attuale (AES)
|
||||||
|
return Encrypt(newPlainText);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string DecryptLegacyFormat(string encryptedText)
|
||||||
|
{
|
||||||
|
// Su Windows, prova prima ProtectedData
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return DecryptWithProtectedData(encryptedText);
|
||||||
|
}
|
||||||
|
catch (CryptographicException ex) when (ex.Message.Contains("Chiave non utilizzabile") ||
|
||||||
|
ex.Message.Contains("Key not valid for use in specified state") ||
|
||||||
|
ex.Message.Contains("The data is invalid"))
|
||||||
|
{
|
||||||
|
// ProtectedData non riesce (probabilmente diversa macchina/utente)
|
||||||
|
// Prova con AES come fallback
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return DecryptWithAes(encryptedText);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Impossibile decrittografare le credenziali. " +
|
||||||
|
"Le credenziali potrebbero essere state create su una macchina/utente diverso. " +
|
||||||
|
"È necessario re-inserire le credenziali.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Su altre piattaforme, usa AES
|
||||||
|
return DecryptWithAes(encryptedText);
|
||||||
|
}
|
||||||
} private string EncryptWithProtectedData(string plainText)
|
} private string EncryptWithProtectedData(string plainText)
|
||||||
{
|
{
|
||||||
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
|
||||||
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
|
byte[] entropy = Encoding.UTF8.GetBytes(ENTROPY_STRING);
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
@@ -95,7 +161,7 @@ public class EncryptionService : IEncryptionService
|
|||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
|
byte[] encryptedBytes = Convert.FromBase64String(encryptedText);
|
||||||
byte[] entropy = Encoding.UTF8.GetBytes("CredentialManager2025");
|
byte[] entropy = Encoding.UTF8.GetBytes(ENTROPY_STRING);
|
||||||
byte[] decryptedBytes = ProtectedData.Unprotect(encryptedBytes, entropy, DataProtectionScope.CurrentUser);
|
byte[] decryptedBytes = ProtectedData.Unprotect(encryptedBytes, entropy, DataProtectionScope.CurrentUser);
|
||||||
return Encoding.UTF8.GetString(decryptedBytes);
|
return Encoding.UTF8.GetString(decryptedBytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,34 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@inject IDataConnectionCredentialService CredentialService
|
@inject IDataConnectionCredentialService CredentialService
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<PageTitle>Gestione Credenziali</PageTitle>
|
<PageTitle>Gestione Credenziali</PageTitle>
|
||||||
|
|
||||||
<h3>Gestione Credenziali</h3>
|
<h3>Gestione Credenziali</h3>
|
||||||
|
|
||||||
|
@* Controllo per credenziali problematiche *@
|
||||||
|
@if (hasProblematicCredentials && !loading)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning mb-4" role="alert">
|
||||||
|
<h4 class="alert-heading"><i class="oi oi-warning"></i> Attenzione!</h4>
|
||||||
|
<p>
|
||||||
|
Sono state rilevate credenziali che non possono essere decrittografate.
|
||||||
|
Questo può accadere quando l'applicazione viene eseguita su una macchina o con un utente diverso
|
||||||
|
da quello utilizzato per creare le credenziali.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">
|
||||||
|
<button class="btn btn-warning" @onclick="@(() => Navigation.NavigateTo("/credential-migration"))">
|
||||||
|
<i class="oi oi-wrench"></i> Risolvi Problema Credenziali
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-warning ms-2" @onclick="CheckForProblematicCredentials">
|
||||||
|
<i class="oi oi-reload"></i> Verifica Nuovamente
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
@@ -519,6 +542,7 @@ else
|
|||||||
private bool loading = true;
|
private bool loading = true;
|
||||||
private string? errorMessage = null;
|
private string? errorMessage = null;
|
||||||
private bool testingConnection = false;
|
private bool testingConnection = false;
|
||||||
|
private bool hasProblematicCredentials = false;
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
private bool showDatabaseModal = false;
|
private bool showDatabaseModal = false;
|
||||||
@@ -529,16 +553,16 @@ else
|
|||||||
private RestApiCredential currentRestApiCredential = new();
|
private RestApiCredential currentRestApiCredential = new();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{ await RefreshCredentials();
|
||||||
await RefreshCredentials();
|
CheckForProblematicCredentials();
|
||||||
} private async Task RefreshCredentials()
|
} private async Task RefreshCredentials()
|
||||||
{
|
{
|
||||||
loading = true;
|
loading = true;
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
try
|
try
|
||||||
{
|
{ databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
||||||
databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
|
||||||
restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync();
|
restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync();
|
||||||
|
CheckForProblematicCredentials();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -957,6 +981,56 @@ else
|
|||||||
};
|
};
|
||||||
|
|
||||||
return IsFieldRequired(fieldName, serviceType) ? $"{label} *" : label;
|
return IsFieldRequired(fieldName, serviceType) ? $"{label} *" : label;
|
||||||
|
} /// <summary>
|
||||||
|
/// Verifica se ci sono credenziali che non possono essere decrittografate
|
||||||
|
/// </summary>
|
||||||
|
private void CheckForProblematicCredentials()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
hasProblematicCredentials = false;
|
||||||
|
|
||||||
|
// Verifica credenziali database
|
||||||
|
foreach (var dbCred in databaseCredentials)
|
||||||
|
{
|
||||||
|
if (HasProblematicPassword(dbCred.Password))
|
||||||
|
{
|
||||||
|
hasProblematicCredentials = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica credenziali REST API se non trovate problematiche
|
||||||
|
if (!hasProblematicCredentials)
|
||||||
|
{
|
||||||
|
foreach (var restCred in restApiCredentials)
|
||||||
|
{
|
||||||
|
if (HasProblematicPassword(restCred.Password) ||
|
||||||
|
HasProblematicPassword(restCred.ApiKey) ||
|
||||||
|
HasProblematicPassword(restCred.AuthToken) ||
|
||||||
|
HasProblematicPassword(restCred.ClientSecret))
|
||||||
|
{
|
||||||
|
hasProblematicCredentials = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log dell'errore, ma non bloccare l'interfaccia
|
||||||
|
Console.WriteLine($"Errore nella verifica delle credenziali problematiche: {ex.Message}");
|
||||||
|
}
|
||||||
|
} /// <summary>
|
||||||
|
/// Verifica se una password indica un problema di decrittografia
|
||||||
|
/// </summary>
|
||||||
|
private bool HasProblematicPassword(string? password)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(password) &&
|
||||||
|
(password.Contains("*** CREDENZIALI NON DISPONIBILI") ||
|
||||||
|
password.Contains("*** ERRORE DECRITTOGRAFIA ***"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -0,0 +1,333 @@
|
|||||||
|
@page "/credential-migration"
|
||||||
|
@using CredentialManager.Services
|
||||||
|
@using CredentialManager.Models
|
||||||
|
@inject ICredentialService CredentialService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
@inject ILogger<CredentialMigration> Logger
|
||||||
|
|
||||||
|
<PageTitle>Migrazione Credenziali</PageTitle>
|
||||||
|
|
||||||
|
<div class="container-fluid mt-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h3><i class="fas fa-exclamation-triangle"></i> Migrazione Credenziali Richiesta</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h4 class="alert-heading">Credenziali non disponibili</h4>
|
||||||
|
<p>
|
||||||
|
Le credenziali salvate sono state crittografate su un'altra macchina/utente e non possono essere decrittografate su questo sistema.
|
||||||
|
Questo è normale quando l'applicazione viene eseguita su una macchina diversa da quella su cui sono state create le credenziali.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">
|
||||||
|
È necessario re-inserire le credenziali che non possono essere decrittografate.
|
||||||
|
Questo processo è sicuro e mantiene tutti gli altri dati delle credenziali intatti.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (isLoading)
|
||||||
|
{
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<div class="spinner-border" role="status">
|
||||||
|
<span class="visually-hidden">Caricamento...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (problematicCredentials.Any())
|
||||||
|
{
|
||||||
|
<h5>Credenziali che richiedono re-inserimento:</h5>
|
||||||
|
|
||||||
|
@foreach (var credGroup in problematicCredentials.GroupBy(c => c.Type))
|
||||||
|
{
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h6 class="mb-0">@GetCredentialTypeDisplayName(credGroup.Key)</h6>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
@foreach (var cred in credGroup)
|
||||||
|
{
|
||||||
|
<div class="border rounded p-3 mb-3">
|
||||||
|
<h6>@cred.Name</h6>
|
||||||
|
<p class="text-muted">@cred.Description</p> @if (cred.Type == CredentialType.Database)
|
||||||
|
{
|
||||||
|
var editModel = GetEditModel(cred.Name);
|
||||||
|
<EditForm Model="@editModel" OnValidSubmit="@(() => UpdateDatabaseCredential(cred.Name))">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<ValidationSummary />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password Database:</label>
|
||||||
|
<InputText @bind-Value="editModel.Password" type="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i> Aggiorna Credenziali
|
||||||
|
</button>
|
||||||
|
</EditForm>
|
||||||
|
} else if (cred.Type == CredentialType.RestApi)
|
||||||
|
{
|
||||||
|
var editModel = GetEditModel(cred.Name);
|
||||||
|
<EditForm Model="@editModel" OnValidSubmit="@(() => UpdateRestCredential(cred.Name))">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<ValidationSummary />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Password:</label>
|
||||||
|
<InputText @bind-Value="editModel.Password" type="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">API Key (se applicabile):</label>
|
||||||
|
<InputText @bind-Value="editModel.ApiKey" type="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (IsSpecificServiceType(cred, "Salesforce"))
|
||||||
|
{
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Client Secret:</label>
|
||||||
|
<InputText @bind-Value="editModel.ClientSecret" type="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Security Token:</label>
|
||||||
|
<InputText @bind-Value="editModel.SecurityToken" type="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save"></i> Aggiorna Credenziali
|
||||||
|
</button>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<h4 class="alert-heading">Ottimo!</h4>
|
||||||
|
<p>Tutte le credenziali sono state aggiornate con successo e possono essere decrittografate correttamente.</p>
|
||||||
|
<hr>
|
||||||
|
<a href="/credentials" class="btn btn-success">Torna alla Gestione Credenziali</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private bool isLoading = true;
|
||||||
|
private List<CredentialSummary> problematicCredentials = new();
|
||||||
|
private Dictionary<string, CredentialEditModel> editModels = new();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await LoadProblematicCredentials();
|
||||||
|
isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadProblematicCredentials()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
problematicCredentials.Clear();
|
||||||
|
|
||||||
|
// Carica tutte le credenziali e verifica quelle problematiche
|
||||||
|
var allCredentials = new List<CredentialSummary>();
|
||||||
|
|
||||||
|
// Database credentials
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dbCreds = await CredentialService.GetAllDatabaseCredentialsAsync();
|
||||||
|
allCredentials.AddRange(dbCreds.Select(c => new CredentialSummary
|
||||||
|
{
|
||||||
|
Name = c.Name,
|
||||||
|
Type = CredentialType.Database,
|
||||||
|
Description = $"Database: {c.DatabaseName} su {c.Host}",
|
||||||
|
HasProblematicPassword = NeedsReentry(c.Password)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nel caricamento credenziali database");
|
||||||
|
}
|
||||||
|
|
||||||
|
// REST API credentials
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var restCreds = await CredentialService.GetAllRestApiCredentialsAsync();
|
||||||
|
allCredentials.AddRange(restCreds.Select(c => new CredentialSummary
|
||||||
|
{
|
||||||
|
Name = c.Name,
|
||||||
|
Type = CredentialType.RestApi,
|
||||||
|
Description = $"Servizio: {c.ServiceType} - {c.BaseUrl}",
|
||||||
|
ServiceType = c.ServiceType.ToString(),
|
||||||
|
HasProblematicPassword = NeedsReentry(c.Password ?? ""),
|
||||||
|
HasProblematicApiKey = NeedsReentry(c.ApiKey ?? ""),
|
||||||
|
HasProblematicClientSecret = NeedsReentry(c.ClientSecret ?? "")
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nel caricamento credenziali REST API");
|
||||||
|
}
|
||||||
|
|
||||||
|
problematicCredentials = allCredentials
|
||||||
|
.Where(c => c.HasProblematicPassword || c.HasProblematicApiKey || c.HasProblematicClientSecret)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Inizializza i modelli di editing
|
||||||
|
foreach (var cred in problematicCredentials)
|
||||||
|
{
|
||||||
|
editModels[cred.Name] = new CredentialEditModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore generale nel caricamento delle credenziali");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NeedsReentry(string credentialValue)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(credentialValue) &&
|
||||||
|
(credentialValue.Contains("*** CREDENZIALI NON DISPONIBILI") ||
|
||||||
|
credentialValue.Contains("*** ERRORE DECRITTOGRAFIA ***"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCredentialTypeDisplayName(CredentialType type)
|
||||||
|
{
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
CredentialType.Database => "Credenziali Database",
|
||||||
|
CredentialType.RestApi => "Credenziali REST API",
|
||||||
|
_ => type.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsSpecificServiceType(CredentialSummary cred, string serviceType)
|
||||||
|
{
|
||||||
|
return cred.ServiceType?.Contains(serviceType, StringComparison.OrdinalIgnoreCase) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CredentialEditModel GetEditModel(string credentialName)
|
||||||
|
{
|
||||||
|
if (!editModels.ContainsKey(credentialName))
|
||||||
|
editModels[credentialName] = new CredentialEditModel();
|
||||||
|
return editModels[credentialName];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateDatabaseCredential(string credentialName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var editModel = GetEditModel(credentialName);
|
||||||
|
if (string.IsNullOrEmpty(editModel.Password))
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", "La password è obbligatoria");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trova la credenziale esistente
|
||||||
|
var existingCred = (await CredentialService.GetAllDatabaseCredentialsAsync())
|
||||||
|
.FirstOrDefault(c => c.Name == credentialName);
|
||||||
|
|
||||||
|
if (existingCred != null)
|
||||||
|
{
|
||||||
|
// Aggiorna solo la password
|
||||||
|
existingCred.Password = editModel.Password;
|
||||||
|
await CredentialService.SaveDatabaseCredentialAsync(existingCred);
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", "Credenziali aggiornate con successo!");
|
||||||
|
await LoadProblematicCredentials();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nell'aggiornamento delle credenziali database: {Name}", credentialName);
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nell'aggiornamento: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateRestCredential(string credentialName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var editModel = GetEditModel(credentialName);
|
||||||
|
|
||||||
|
// Trova la credenziale esistente
|
||||||
|
var existingCred = (await CredentialService.GetAllRestApiCredentialsAsync())
|
||||||
|
.FirstOrDefault(c => c.Name == credentialName);
|
||||||
|
|
||||||
|
if (existingCred != null)
|
||||||
|
{
|
||||||
|
// Aggiorna i campi forniti
|
||||||
|
if (!string.IsNullOrEmpty(editModel.Password))
|
||||||
|
existingCred.Password = editModel.Password;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(editModel.ApiKey))
|
||||||
|
existingCred.ApiKey = editModel.ApiKey;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(editModel.ClientSecret))
|
||||||
|
existingCred.ClientSecret = editModel.ClientSecret;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(editModel.SecurityToken))
|
||||||
|
existingCred.SecurityToken = editModel.SecurityToken;
|
||||||
|
|
||||||
|
await CredentialService.SaveRestApiCredentialAsync(existingCred);
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", "Credenziali aggiornate con successo!");
|
||||||
|
await LoadProblematicCredentials();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Errore nell'aggiornamento delle credenziali REST: {Name}", credentialName);
|
||||||
|
await JSRuntime.InvokeVoidAsync("alert", $"Errore nell'aggiornamento: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CredentialSummary
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public CredentialType Type { get; set; }
|
||||||
|
public string Description { get; set; } = "";
|
||||||
|
public string? ServiceType { get; set; }
|
||||||
|
public bool HasProblematicPassword { get; set; }
|
||||||
|
public bool HasProblematicApiKey { get; set; }
|
||||||
|
public bool HasProblematicClientSecret { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CredentialEditModel
|
||||||
|
{
|
||||||
|
public string? Password { get; set; }
|
||||||
|
public string? ApiKey { get; set; }
|
||||||
|
public string? ClientSecret { get; set; }
|
||||||
|
public string? SecurityToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user