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:
@@ -6,11 +6,34 @@
|
||||
@using Microsoft.JSInterop
|
||||
@inject IDataConnectionCredentialService CredentialService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>Gestione Credenziali</PageTitle>
|
||||
|
||||
<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="col">
|
||||
<div class="btn-group" role="group">
|
||||
@@ -519,6 +542,7 @@ else
|
||||
private bool loading = true;
|
||||
private string? errorMessage = null;
|
||||
private bool testingConnection = false;
|
||||
private bool hasProblematicCredentials = false;
|
||||
|
||||
// Modal state
|
||||
private bool showDatabaseModal = false;
|
||||
@@ -529,16 +553,16 @@ else
|
||||
private RestApiCredential currentRestApiCredential = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshCredentials();
|
||||
{ await RefreshCredentials();
|
||||
CheckForProblematicCredentials();
|
||||
} private async Task RefreshCredentials()
|
||||
{
|
||||
loading = true;
|
||||
errorMessage = null;
|
||||
try
|
||||
{
|
||||
databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
||||
{ databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync();
|
||||
restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync();
|
||||
CheckForProblematicCredentials();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -957,6 +981,56 @@ else
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
@@ -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