mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
54c2c80904
The DevPrep shadow validator was never executed because the ValidateSecurityAsync extension constructed SecurityValidator without passing DeviceAssociationValidator, so _deviceAssociationValidator was always null and the gate at SecurityValidator.cs:370 silently skipped the lookup — even with ValidateDeviceAssociation=true on the tenant. Inject the validator via DI into all 9 agent-facing functions (RegisterSession, IngestEvents, GetAgentConfig, ReportAgentError, GetDiagnosticsUploadUrl + 4 Bootstrap variants) and thread it through the extension to SecurityValidator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
102 lines
4.4 KiB
C#
102 lines
4.4 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Threading.Tasks;
|
|
using AutopilotMonitor.Functions.Services;
|
|
using AutopilotMonitor.Shared.Models;
|
|
using Microsoft.Azure.Functions.Worker.Http;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace AutopilotMonitor.Functions.Security
|
|
{
|
|
/// <summary>
|
|
/// Extension methods for security validation on HTTP requests
|
|
/// </summary>
|
|
public static class SecurityValidationExtensions
|
|
{
|
|
/// <summary>
|
|
/// Validates request security and creates error response if validation fails
|
|
/// </summary>
|
|
/// <param name="req">HTTP request</param>
|
|
/// <param name="tenantId">Tenant ID</param>
|
|
/// <param name="configService">Tenant configuration service</param>
|
|
/// <param name="rateLimitService">Rate limit service</param>
|
|
/// <param name="logger">Logger</param>
|
|
/// <returns>Validation result with optional error response</returns>
|
|
public static async Task<(SecurityValidationResult validation, HttpResponseData? errorResponse)> ValidateSecurityAsync(
|
|
this HttpRequestData req,
|
|
string tenantId,
|
|
TenantConfigurationService configService,
|
|
RateLimitService rateLimitService,
|
|
AutopilotDeviceValidator autopilotDeviceValidator,
|
|
CorporateIdentifierValidator corporateIdentifierValidator,
|
|
ILogger logger,
|
|
string? sessionId = null,
|
|
BootstrapSessionService? bootstrapSessionService = null,
|
|
DeviceAssociationValidator? deviceAssociationValidator = null)
|
|
{
|
|
var validator = new SecurityValidator(configService, rateLimitService, autopilotDeviceValidator, corporateIdentifierValidator, logger, bootstrapSessionService, deviceAssociationValidator);
|
|
var validation = await validator.ValidateRequestAsync(req, tenantId, sessionId);
|
|
|
|
if (!validation.IsValid)
|
|
{
|
|
// Create appropriate error response
|
|
var response = req.CreateResponse(validation.StatusCode);
|
|
|
|
if (validation.StatusCode == HttpStatusCode.TooManyRequests)
|
|
{
|
|
// Rate limit error - include Retry-After header
|
|
if (validation.RateLimitResult?.RetryAfter.HasValue == true)
|
|
{
|
|
response.Headers.Add("Retry-After", ((int)validation.RateLimitResult.RetryAfter.Value.TotalSeconds).ToString());
|
|
}
|
|
|
|
await response.WriteAsJsonAsync(new
|
|
{
|
|
success = false,
|
|
message = validation.ErrorMessage,
|
|
rateLimitExceeded = true,
|
|
rateLimitInfo = new
|
|
{
|
|
requestsInWindow = validation.RateLimitResult?.RequestsInWindow,
|
|
maxRequests = validation.RateLimitResult?.MaxRequests,
|
|
windowDurationSeconds = validation.RateLimitResult?.WindowDuration.TotalSeconds,
|
|
retryAfterSeconds = validation.RateLimitResult?.RetryAfter?.TotalSeconds
|
|
}
|
|
});
|
|
}
|
|
else if (validation.StatusCode == HttpStatusCode.ServiceUnavailable)
|
|
{
|
|
// Transient device validation failure — tell agent to retry
|
|
if (validation.RetryAfterSeconds.HasValue)
|
|
{
|
|
response.Headers.Add("Retry-After", validation.RetryAfterSeconds.Value.ToString());
|
|
}
|
|
|
|
await response.WriteAsJsonAsync(new
|
|
{
|
|
success = false,
|
|
message = validation.ErrorMessage,
|
|
details = validation.Details,
|
|
retryAfterSeconds = validation.RetryAfterSeconds
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Other security errors
|
|
await response.WriteAsJsonAsync(new
|
|
{
|
|
success = false,
|
|
message = validation.ErrorMessage,
|
|
details = validation.Details
|
|
});
|
|
}
|
|
|
|
return (validation, response);
|
|
}
|
|
|
|
// Validation successful - no error response
|
|
return (validation, null);
|
|
}
|
|
}
|
|
}
|