83 lines
2.9 KiB
C#
83 lines
2.9 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
public class PhiAccessLogService : IPhiAccessLogService
|
|
{
|
|
private readonly AppDbContext _db;
|
|
private readonly ICurrentUserService _currentUser;
|
|
private readonly IHttpContextAccessor _http;
|
|
private readonly PhiEncryptionOptions _options;
|
|
private readonly ClinicalMetrics _metrics;
|
|
|
|
public PhiAccessLogService(
|
|
AppDbContext db,
|
|
ICurrentUserService currentUser,
|
|
IHttpContextAccessor http,
|
|
IOptions<PhiEncryptionOptions> options,
|
|
ClinicalMetrics metrics)
|
|
{
|
|
_db = db;
|
|
_currentUser = currentUser;
|
|
_http = http;
|
|
_options = options.Value;
|
|
_metrics = metrics;
|
|
}
|
|
|
|
public async Task LogViewAsync(Guid patientId, string resourcePath) =>
|
|
await WriteAsync(PhiAccessType.View, patientId, resourcePath);
|
|
|
|
public async Task LogCreateAsync(Guid patientId, string resourcePath) =>
|
|
await WriteAsync(PhiAccessType.Create, patientId, resourcePath);
|
|
|
|
public async Task LogUpdateAsync(Guid patientId, string resourcePath) =>
|
|
await WriteAsync(PhiAccessType.Update, patientId, resourcePath);
|
|
|
|
public async Task LogListAsync(string resourcePath, int resultCount, string? searchQuery = null)
|
|
{
|
|
if (!_options.LogListAccess)
|
|
return;
|
|
|
|
var accessType = string.IsNullOrWhiteSpace(searchQuery)
|
|
? PhiAccessType.List
|
|
: PhiAccessType.Search;
|
|
|
|
await WriteAsync(accessType, null, resourcePath, resultCount, searchQuery);
|
|
}
|
|
|
|
private async Task WriteAsync(
|
|
PhiAccessType accessType,
|
|
Guid? patientId,
|
|
string resourcePath,
|
|
int? resultCount = null,
|
|
string? searchQuery = null)
|
|
{
|
|
if (!_currentUser.IsAuthenticated || _currentUser.UserId is null)
|
|
return; // machine/integration paths may skip — Phase 31 Integration role should still auth
|
|
|
|
_db.PhiAccessLogs.Add(new PhiAccessLog
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
AccessType = accessType,
|
|
PatientId = patientId,
|
|
UserId = _currentUser.UserId.Value,
|
|
UserDisplayName = _currentUser.DisplayName ?? _currentUser.Username ?? "Unknown",
|
|
ResourcePath = resourcePath,
|
|
SearchQueryHash = searchQuery is null ? null : HashQuery(searchQuery),
|
|
ResultCount = resultCount,
|
|
IpAddress = _currentUser.IpAddress,
|
|
CorrelationId = _http.HttpContext?.Items["CorrelationId"]?.ToString(),
|
|
AccessedAt = DateTimeOffset.UtcNow
|
|
});
|
|
|
|
await _db.SaveChangesAsync();
|
|
|
|
_metrics.PhiAccessLogsTotal.WithLabels(accessType.ToDbString()).Inc();
|
|
}
|
|
|
|
private static string HashQuery(string query)
|
|
{
|
|
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(query.Trim().ToLowerInvariant()));
|
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
|
}
|
|
} |