210 lines
7.3 KiB
C#
210 lines
7.3 KiB
C#
using System.Reflection;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using GestionaDenunciasAN.Models;
|
|
using GestionaDenunciasAN.Services;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
|
|
namespace ApiDenuncias.Services;
|
|
|
|
public sealed class EncryptedDenunciaStore : IDenunciaStore
|
|
{
|
|
private const string ProtectedStringPrefix = "enc:v1:";
|
|
private static readonly byte[] ProtectedBytesPrefix = Encoding.ASCII.GetBytes("enc:v1:");
|
|
private static readonly PropertyInfo[] ComplaintProperties = typeof(DenunciasGestiona)
|
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
|
.Where(property => property.CanRead && property.CanWrite)
|
|
.ToArray();
|
|
|
|
private readonly MySqlDenunciaStore _inner;
|
|
private readonly IDataProtector _protector;
|
|
|
|
public EncryptedDenunciaStore(MySqlDenunciaStore inner, IDataProtectionProvider dataProtectionProvider)
|
|
{
|
|
_inner = inner;
|
|
_protector = dataProtectionProvider.CreateProtector("ApiDenuncias.DatabaseSensitiveData.v1");
|
|
}
|
|
|
|
public Task EnsureSchemaAsync(CancellationToken cancellationToken = default)
|
|
=> _inner.EnsureSchemaAsync(cancellationToken);
|
|
|
|
public async Task<List<DenunciasGestiona>> GetAllDenunciasAsync(CancellationToken cancellationToken = default)
|
|
=> (await _inner.GetAllDenunciasAsync(cancellationToken))
|
|
.Select(UnprotectComplaint)
|
|
.ToList();
|
|
|
|
public async Task<List<FicherosDenuncias>> GetAllFicherosAsync(CancellationToken cancellationToken = default)
|
|
=> (await _inner.GetAllFicherosAsync(cancellationToken))
|
|
.Select(UnprotectAttachment)
|
|
.ToList();
|
|
|
|
public async Task<List<FicherosDenuncias>> GetFicherosByDenunciaAsync(int denunciaId, CancellationToken cancellationToken = default)
|
|
=> (await _inner.GetFicherosByDenunciaAsync(denunciaId, cancellationToken))
|
|
.Select(UnprotectAttachment)
|
|
.ToList();
|
|
|
|
public async Task<DenunciasGestiona?> GetDenunciaByIdAsync(int denunciaId, CancellationToken cancellationToken = default)
|
|
{
|
|
var denuncia = await _inner.GetDenunciaByIdAsync(denunciaId, cancellationToken);
|
|
return denuncia is null ? null : UnprotectComplaint(denuncia);
|
|
}
|
|
|
|
public Task UpsertDenunciaAsync(DenunciasGestiona denuncia, CancellationToken cancellationToken = default)
|
|
=> _inner.UpsertDenunciaAsync(ProtectComplaint(denuncia), cancellationToken);
|
|
|
|
public Task UpsertFicherosAsync(IEnumerable<FicherosDenuncias> ficheros, CancellationToken cancellationToken = default)
|
|
=> _inner.UpsertFicherosAsync(ficheros.Select(ProtectAttachment).ToArray(), cancellationToken);
|
|
|
|
public Task MarkFicherosAsUploadedAsync(
|
|
int denunciaId,
|
|
IEnumerable<string> fileNames,
|
|
DateTime uploadedAtUtc,
|
|
CancellationToken cancellationToken = default)
|
|
=> _inner.MarkFicherosAsUploadedAsync(denunciaId, fileNames, uploadedAtUtc, cancellationToken);
|
|
|
|
private DenunciasGestiona ProtectComplaint(DenunciasGestiona source)
|
|
=> TransformComplaint(source, ProtectString);
|
|
|
|
private DenunciasGestiona UnprotectComplaint(DenunciasGestiona source)
|
|
=> TransformComplaint(source, UnprotectString);
|
|
|
|
private static DenunciasGestiona TransformComplaint(DenunciasGestiona source, Func<string, string> transformString)
|
|
{
|
|
var target = new DenunciasGestiona();
|
|
|
|
foreach (var property in ComplaintProperties)
|
|
{
|
|
var value = property.GetValue(source);
|
|
if (property.PropertyType == typeof(string))
|
|
{
|
|
property.SetValue(target, transformString((string?)value ?? string.Empty));
|
|
}
|
|
else
|
|
{
|
|
property.SetValue(target, value);
|
|
}
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
private FicherosDenuncias ProtectAttachment(FicherosDenuncias source)
|
|
{
|
|
var content = source.Fichero ?? [];
|
|
var hash = string.IsNullOrWhiteSpace(source.ContentSha256)
|
|
? ComputeSha256Hex(content)
|
|
: source.ContentSha256.Trim().ToLowerInvariant();
|
|
|
|
return new FicherosDenuncias
|
|
{
|
|
Id_Fichero = source.Id_Fichero,
|
|
Id_Tipo = source.Id_Tipo,
|
|
Descripcion = ProtectString(source.Descripcion ?? string.Empty),
|
|
Fecha = source.Fecha,
|
|
Observaciones = ProtectString(source.Observaciones ?? string.Empty),
|
|
Id_Denuncia = source.Id_Denuncia,
|
|
NombreFichero = source.NombreFichero,
|
|
Fichero = ProtectBytes(content),
|
|
Subido = source.Subido,
|
|
FechaSubida = source.FechaSubida,
|
|
ContentSha256 = hash
|
|
};
|
|
}
|
|
|
|
private FicherosDenuncias UnprotectAttachment(FicherosDenuncias source)
|
|
{
|
|
return new FicherosDenuncias
|
|
{
|
|
Id_Fichero = source.Id_Fichero,
|
|
Id_Tipo = source.Id_Tipo,
|
|
Descripcion = UnprotectString(source.Descripcion ?? string.Empty),
|
|
Fecha = source.Fecha,
|
|
Observaciones = UnprotectString(source.Observaciones ?? string.Empty),
|
|
Id_Denuncia = source.Id_Denuncia,
|
|
NombreFichero = source.NombreFichero,
|
|
Fichero = UnprotectBytes(source.Fichero ?? []),
|
|
Subido = source.Subido,
|
|
FechaSubida = source.FechaSubida,
|
|
ContentSha256 = source.ContentSha256
|
|
};
|
|
}
|
|
|
|
private string ProtectString(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value) || value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return ProtectedStringPrefix + _protector.Protect(value);
|
|
}
|
|
|
|
private string UnprotectString(string value)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(value) || !value.StartsWith(ProtectedStringPrefix, StringComparison.Ordinal))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
try
|
|
{
|
|
return _protector.Unprotect(value[ProtectedStringPrefix.Length..]);
|
|
}
|
|
catch
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
private byte[] ProtectBytes(byte[] value)
|
|
{
|
|
if (value.Length == 0 || StartsWith(value, ProtectedBytesPrefix))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var protectedBytes = _protector.Protect(value);
|
|
var base64Bytes = Encoding.ASCII.GetBytes(Convert.ToBase64String(protectedBytes));
|
|
return [.. ProtectedBytesPrefix, .. base64Bytes];
|
|
}
|
|
|
|
private byte[] UnprotectBytes(byte[] value)
|
|
{
|
|
if (value.Length == 0 || !StartsWith(value, ProtectedBytesPrefix))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
try
|
|
{
|
|
var base64 = Encoding.ASCII.GetString(value, ProtectedBytesPrefix.Length, value.Length - ProtectedBytesPrefix.Length);
|
|
return _protector.Unprotect(Convert.FromBase64String(base64));
|
|
}
|
|
catch
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
private static bool StartsWith(byte[] value, byte[] prefix)
|
|
{
|
|
if (value.Length < prefix.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < prefix.Length; i++)
|
|
{
|
|
if (value[i] != prefix[i])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static string ComputeSha256Hex(byte[] content)
|
|
=> Convert.ToHexString(SHA256.HashData(content)).ToLowerInvariant();
|
|
}
|