Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@

app.MapGet("/devices/{id:int}", (int id, DeviceService service) =>
{
if (id <= 0)
{
return Results.BadRequest(new { message = "Device ID must be greater than zero." });
}
Comment on lines +132 to +135

var device = service.GetDevice(id);

return device is null
Expand Down Expand Up @@ -199,13 +204,13 @@
"application/dicom",
StringComparison.OrdinalIgnoreCase) != true)
{
await repository.AddAuditEventAsync("dicom.inspect", "unsupported-media-type", cancellationToken);
await TryAuditAsync(repository, "dicom.inspect", "unsupported-media-type", cancellationToken);
return Results.StatusCode(StatusCodes.Status415UnsupportedMediaType);
}

if (request.ContentLength is > MaxDicomUploadBytes)
{
await repository.AddAuditEventAsync("dicom.inspect", "payload-too-large", cancellationToken);
await TryAuditAsync(repository, "dicom.inspect", "payload-too-large", cancellationToken);
return Results.StatusCode(StatusCodes.Status413PayloadTooLarge);
}

Expand Down Expand Up @@ -233,7 +238,7 @@

if (total == 0)
{
await repository.AddAuditEventAsync("dicom.inspect", "empty-body", cancellationToken);
await TryAuditAsync(repository, "dicom.inspect", "empty-body", cancellationToken);
return Results.BadRequest(new { message = "A DICOM request body is required." });
}

Expand All @@ -242,15 +247,34 @@
try
{
var inspection = service.Inspect(buffer);
await repository.AddInspectionAsync(inspection, cancellationToken);
await repository.AddAuditEventAsync("dicom.inspect", "success", cancellationToken);
try
{
await repository.AddInspectionAsync(inspection, cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
await TryAuditAsync(repository, "dicom.inspect", "success", cancellationToken);
return Results.Ok(inspection);
}
catch (FellowOakDicom.DicomFileException)
{
await repository.AddAuditEventAsync("dicom.inspect", "invalid-dicom", cancellationToken);
await TryAuditAsync(repository, "dicom.inspect", "invalid-dicom", cancellationToken);
return Results.BadRequest(new { message = "The request body is not a readable DICOM file." });
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception)
{
await TryAuditAsync(repository, "dicom.inspect", "processing-error", CancellationToken.None);
return Results.Problem(
statusCode: StatusCodes.Status500InternalServerError,
title: "DICOM inspection failed.",
detail: "The DICOM document could not be processed.");
}
}).RequireRateLimiting("api-write");

app.MapGet("/dicom/admin/inspections", async (
Expand All @@ -262,11 +286,11 @@
{
if (!IsAuthorized(request, configuration))
{
await repository.AddAuditEventAsync("dicom.admin.inspections", "unauthorized", cancellationToken);
await TryAuditAsync(repository, "dicom.admin.inspections", "unauthorized", cancellationToken);
return Results.Unauthorized();
}

await repository.AddAuditEventAsync("dicom.admin.inspections", "success", cancellationToken);
await TryAuditAsync(repository, "dicom.admin.inspections", "success", cancellationToken);
var records = await repository.GetRecentAsync(take ?? 25, cancellationToken);
return Results.Ok(records);
}).RequireRateLimiting("api-write");
Expand All @@ -291,6 +315,22 @@

app.Run();

static async Task TryAuditAsync(IDicomMetadataRepository repository, string eventType, string outcome, CancellationToken cancellationToken)
{
try
{
await repository.AddAuditEventAsync(eventType, outcome, cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch
{
// Audit persistence must never turn a request into an application failure.
}
}

static bool IsAuthorized(HttpRequest request, IConfiguration configuration)
{
var configuredKey = configuration["Security:ApiKey"];
Expand Down
Loading