270 lines
8.0 KiB
C#
270 lines
8.0 KiB
C#
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Security.Cryptography;
|
|
using GestionaDenuncias.Shared.Models;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
|
|
namespace ApiDenuncias.Services;
|
|
|
|
public sealed class GlobalLeaksSessionStore
|
|
{
|
|
private const string RootPath = @"C:\GestionaDenuncias\.gl-auth";
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
|
{
|
|
WriteIndented = false,
|
|
};
|
|
|
|
private readonly SemaphoreSlim _gate = new(1, 1);
|
|
private readonly IDataProtector _protector;
|
|
|
|
public GlobalLeaksSessionStore(IDataProtectionProvider dataProtectionProvider)
|
|
{
|
|
_protector = dataProtectionProvider.CreateProtector("GestionaDenunciasAN.GlobalLeaksSessionStore");
|
|
}
|
|
|
|
public async Task<GlobalLeaksStoredSession?> GetAsync(string username, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(username))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var path = GetFilePath(username);
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
return await ReadUnsafeAsync(path, cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
public async Task SaveAsync(
|
|
string username,
|
|
string password,
|
|
string sessionId,
|
|
string? role,
|
|
string? dpopPrivateKey,
|
|
GlobalLeaksProofOfWorkToken? proofOfWorkToken,
|
|
DateTimeOffset? sessionExpiresAtUtc,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var now = DateTimeOffset.UtcNow;
|
|
var data = new GlobalLeaksStoredSession
|
|
{
|
|
Username = username,
|
|
Password = password,
|
|
SessionId = sessionId,
|
|
Role = role,
|
|
DpopPrivateKey = dpopPrivateKey,
|
|
ProofOfWorkToken = proofOfWorkToken,
|
|
SessionExpiresAtUtc = sessionExpiresAtUtc,
|
|
LastKeepAliveAtUtc = now,
|
|
UpdatedAt = now,
|
|
};
|
|
|
|
await WriteAsync(data, cancellationToken);
|
|
}
|
|
|
|
public async Task UpdateSessionAsync(
|
|
string username,
|
|
string sessionId,
|
|
string? role,
|
|
string? dpopPrivateKey,
|
|
GlobalLeaksProofOfWorkToken? proofOfWorkToken,
|
|
DateTimeOffset? sessionExpiresAtUtc,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var path = GetFilePath(username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
var current = await ReadUnsafeAsync(path, cancellationToken)
|
|
?? throw new InvalidOperationException("No hay credenciales guardadas para este usuario.");
|
|
var now = DateTimeOffset.UtcNow;
|
|
|
|
current.SessionId = sessionId;
|
|
current.Role = role;
|
|
current.DpopPrivateKey = dpopPrivateKey;
|
|
current.ProofOfWorkToken = proofOfWorkToken;
|
|
current.SessionExpiresAtUtc = sessionExpiresAtUtc;
|
|
current.LastKeepAliveAtUtc = now;
|
|
current.UpdatedAt = now;
|
|
|
|
await WriteUnsafeAsync(path, current, cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
public async Task<bool> UpdateKeepAliveAsync(
|
|
string username,
|
|
string expectedSessionId,
|
|
GlobalLeaksProofOfWorkToken proofOfWorkToken,
|
|
DateTimeOffset? sessionExpiresAtUtc,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var path = GetFilePath(username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
var current = await ReadUnsafeAsync(path, cancellationToken);
|
|
if (current is null ||
|
|
!string.Equals(current.SessionId, expectedSessionId, StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var now = DateTimeOffset.UtcNow;
|
|
current.ProofOfWorkToken = proofOfWorkToken;
|
|
current.SessionExpiresAtUtc = sessionExpiresAtUtc;
|
|
current.LastKeepAliveAtUtc = now;
|
|
current.UpdatedAt = now;
|
|
await WriteUnsafeAsync(path, current, cancellationToken);
|
|
return true;
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
public async Task ClearSessionAsync(string username, CancellationToken cancellationToken = default)
|
|
{
|
|
var path = GetFilePath(username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
var current = await ReadUnsafeAsync(path, cancellationToken);
|
|
if (current is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ClearSessionValues(current);
|
|
await WriteUnsafeAsync(path, current, cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
public async Task<bool> ClearSessionIfMatchesAsync(
|
|
string username,
|
|
string expectedSessionId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var path = GetFilePath(username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
var current = await ReadUnsafeAsync(path, cancellationToken);
|
|
if (current is null ||
|
|
!string.Equals(current.SessionId, expectedSessionId, StringComparison.Ordinal))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ClearSessionValues(current);
|
|
await WriteUnsafeAsync(path, current, cancellationToken);
|
|
return true;
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
public async Task DeleteAsync(string username, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(username))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var path = GetFilePath(username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
private async Task WriteAsync(GlobalLeaksStoredSession data, CancellationToken cancellationToken)
|
|
{
|
|
var path = GetFilePath(data.Username);
|
|
|
|
await _gate.WaitAsync(cancellationToken);
|
|
try
|
|
{
|
|
await WriteUnsafeAsync(path, data, cancellationToken);
|
|
}
|
|
finally
|
|
{
|
|
_gate.Release();
|
|
}
|
|
}
|
|
|
|
private static string GetFilePath(string username)
|
|
{
|
|
Directory.CreateDirectory(RootPath);
|
|
var normalized = username.Trim().ToLowerInvariant();
|
|
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(normalized));
|
|
return Path.Combine(RootPath, $"{Convert.ToHexString(hash).ToLowerInvariant()}.bin");
|
|
}
|
|
|
|
private async Task<GlobalLeaksStoredSession?> ReadUnsafeAsync(
|
|
string path,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var protectedBytes = await File.ReadAllBytesAsync(path, cancellationToken);
|
|
var protectedBase64 = Encoding.UTF8.GetString(protectedBytes);
|
|
var json = _protector.Unprotect(protectedBase64);
|
|
return JsonSerializer.Deserialize<GlobalLeaksStoredSession>(json, JsonOptions);
|
|
}
|
|
|
|
private async Task WriteUnsafeAsync(
|
|
string path,
|
|
GlobalLeaksStoredSession data,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Directory.CreateDirectory(RootPath);
|
|
var json = JsonSerializer.Serialize(data, JsonOptions);
|
|
var protectedValue = _protector.Protect(json);
|
|
var protectedBytes = Encoding.UTF8.GetBytes(protectedValue);
|
|
await File.WriteAllBytesAsync(path, protectedBytes, cancellationToken);
|
|
}
|
|
|
|
private static void ClearSessionValues(GlobalLeaksStoredSession session)
|
|
{
|
|
session.SessionId = null;
|
|
session.DpopPrivateKey = null;
|
|
session.ProofOfWorkToken = null;
|
|
session.SessionExpiresAtUtc = null;
|
|
session.LastKeepAliveAtUtc = null;
|
|
session.UpdatedAt = DateTimeOffset.UtcNow;
|
|
}
|
|
}
|