Files
Data-Coupler/Data_Coupler/Pages/CredentialMigration.razor
Alessio Dal Santo 562784e097 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
2025-06-17 12:24:09 +02:00

334 lines
16 KiB
Plaintext

@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; }
}
}