diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index 6ac793b4..ec6cab01 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -325 +331 diff --git a/src/Backend/AutopilotMonitor.Functions.Tests/BuiltInRulesTests.cs b/src/Backend/AutopilotMonitor.Functions.Tests/BuiltInRulesTests.cs index 4ecc0e78..a289f06c 100644 --- a/src/Backend/AutopilotMonitor.Functions.Tests/BuiltInRulesTests.cs +++ b/src/Backend/AutopilotMonitor.Functions.Tests/BuiltInRulesTests.cs @@ -75,7 +75,8 @@ public class BuiltInRulesTests "policiesDiscovered", "ignoreCompletedApp", "imeAgentVersion", "espTrackStatus", "updateName", "updateWin32AppState", "cancelStuckAndSetCurrent", "imeSessionChange", "imeImpersonation", - "enrollmentCompleted" + "enrollmentCompleted", "updateDoTelemetry", "scriptStarted", + "scriptContext", "scriptExitCode", "scriptOutput", "scriptCompleted" }; var patterns = BuiltInImeLogPatterns.GetAll(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/AutopilotDeviceValidationConsentFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/AutopilotDeviceValidationConsentFunction.cs index 5802c571..b12c8098 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/AutopilotDeviceValidationConsentFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/AutopilotDeviceValidationConsentFunction.cs @@ -97,13 +97,7 @@ public class AutopilotDeviceValidationConsentFunction private async Task EnsureAuthorizedTenantAdminAsync(HttpRequestData req, string tenantId) { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { error = "Authentication required." }); - return unauthorized; - } - + // Authentication enforced by PolicyEnforcementMiddleware var authenticatedTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/DeviceBlockFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/DeviceBlockFunction.cs index 619ce527..e74e9d85 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/DeviceBlockFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/DeviceBlockFunction.cs @@ -15,16 +15,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin { private readonly ILogger _logger; private readonly BlockedDeviceService _blockedDeviceService; - private readonly GalacticAdminService _galacticAdminService; public DeviceBlockFunction( ILogger logger, - BlockedDeviceService blockedDeviceService, - GalacticAdminService galacticAdminService) + BlockedDeviceService blockedDeviceService) { _logger = logger; _blockedDeviceService = blockedDeviceService; - _galacticAdminService = galacticAdminService; } /// GET /api/devices/blocked?tenantId={tenantId} — list all active blocks for a tenant @@ -34,12 +31,7 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - - var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var tenantId = req.Query["tenantId"]; if (string.IsNullOrEmpty(tenantId)) @@ -67,12 +59,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); string body; using (var reader = new System.IO.StreamReader(req.Body)) @@ -137,12 +125,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); var tenantId = req.Query["tenantId"]; if (string.IsNullOrEmpty(tenantId)) @@ -175,20 +159,6 @@ namespace AutopilotMonitor.Functions.Functions.Admin // Helpers // ----------------------------------------------------------------------- - private static async Task UnauthorizedAsync(HttpRequestData req) - { - var r = req.CreateResponse(HttpStatusCode.Unauthorized); - await r.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return r; - } - - private static async Task ForbiddenAsync(HttpRequestData req) - { - var r = req.CreateResponse(HttpStatusCode.Forbidden); - await r.WriteAsJsonAsync(new { success = false, message = "Access denied. Only Galactic Admins can manage device blocks." }); - return r; - } - private static async Task BadRequestAsync(HttpRequestData req, string message) { var r = req.CreateResponse(HttpStatusCode.BadRequest); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAllBlockedDevicesFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAllBlockedDevicesFunction.cs index ead851e2..0dcb1f02 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAllBlockedDevicesFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAllBlockedDevicesFunction.cs @@ -1,7 +1,6 @@ using System; using System.Net; using System.Threading.Tasks; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -13,16 +12,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin { private readonly ILogger _logger; private readonly BlockedDeviceService _blockedDeviceService; - private readonly GalacticAdminService _galacticAdminService; public GetAllBlockedDevicesFunction( ILogger logger, - BlockedDeviceService blockedDeviceService, - GalacticAdminService galacticAdminService) + BlockedDeviceService blockedDeviceService) { _logger = logger; _blockedDeviceService = blockedDeviceService; - _galacticAdminService = galacticAdminService; } /// GET /api/galactic/devices/blocked — list all active blocks across all tenants @@ -34,21 +30,7 @@ namespace AutopilotMonitor.Functions.Functions.Admin try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - - var userEmail = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userEmail)) - { - _logger.LogWarning("Non-Galactic Admin user {User} attempted to access GetAllBlockedDevices", userEmail); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." }); - return forbiddenResponse; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var blocked = await _blockedDeviceService.GetAllBlockedDevicesAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAuditLogsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAuditLogsFunction.cs index 277d5a65..4f5e6823 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAuditLogsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetAuditLogsFunction.cs @@ -26,19 +26,7 @@ namespace AutopilotMonitor.Functions.Functions.Admin try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetAuditLogs attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware string tenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetGalacticAuditLogsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetGalacticAuditLogsFunction.cs index 088a3fc8..47b02b8d 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetGalacticAuditLogsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/GetGalacticAuditLogsFunction.cs @@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly GalacticAdminService _galacticAdminService; public GetGalacticAuditLogsFunction( ILogger logger, - TableStorageService storageService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _galacticAdminService = galacticAdminService; } [Function("GetGalacticAuditLogs")] @@ -31,34 +28,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetGalacticAuditLogs attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to access GetGalacticAuditLogs"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } _logger.LogInformation($"Fetching all audit logs across all tenants (User: {userEmail})"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/ReseedFromGitHubFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/ReseedFromGitHubFunction.cs index 4f1a70db..e1b73114 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/ReseedFromGitHubFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/ReseedFromGitHubFunction.cs @@ -19,7 +19,6 @@ namespace AutopilotMonitor.Functions.Functions.Admin private readonly AnalyzeRuleService _analyzeRuleService; private readonly ImeLogPatternService _imeLogPatternService; private readonly TableStorageService _storageService; - private readonly GalacticAdminService _galacticAdminService; public ReseedFromGitHubFunction( ILogger logger, @@ -27,8 +26,7 @@ namespace AutopilotMonitor.Functions.Functions.Admin GatherRuleService gatherRuleService, AnalyzeRuleService analyzeRuleService, ImeLogPatternService imeLogPatternService, - TableStorageService storageService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _gitHubRepo = gitHubRepo; @@ -36,7 +34,6 @@ namespace AutopilotMonitor.Functions.Functions.Admin _analyzeRuleService = analyzeRuleService; _imeLogPatternService = imeLogPatternService; _storageService = storageService; - _galacticAdminService = galacticAdminService; } [Function("ReseedFromGitHub")] @@ -45,20 +42,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var upn = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(upn)) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Galactic Admin privileges required" }); - return forbidden; - } // Parse ?type= parameter (default: all) var typeParam = "all"; diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantAdminManagementFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantAdminManagementFunction.cs index 24dfa9ae..553c7b71 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantAdminManagementFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantAdminManagementFunction.cs @@ -41,11 +41,8 @@ public class TenantAdminManagementFunction string tenantId, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); @@ -84,11 +81,8 @@ public class TenantAdminManagementFunction string tenantId, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); @@ -140,11 +134,8 @@ public class TenantAdminManagementFunction string adminUpn, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); @@ -202,11 +193,8 @@ public class TenantAdminManagementFunction string adminUpn, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); @@ -247,11 +235,8 @@ public class TenantAdminManagementFunction string adminUpn, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); @@ -291,11 +276,8 @@ public class TenantAdminManagementFunction string adminUpn, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs index 2a54aad5..da7b2b91 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TenantOffboardFunction.cs @@ -51,11 +51,8 @@ public class TenantOffboardFunction string tenantId, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } + // Authentication enforced by PolicyEnforcementMiddleware + var principal = context.GetUser()!; var userTenantId = principal.GetTenantId(); var upn = principal.GetUserPrincipalName(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TriggerMaintenanceFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TriggerMaintenanceFunction.cs index 2ffad423..06c3152e 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TriggerMaintenanceFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/TriggerMaintenanceFunction.cs @@ -1,7 +1,6 @@ using System; using System.Net; using System.Threading.Tasks; -using AutopilotMonitor.Functions.Extensions; using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; @@ -17,16 +16,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin { private readonly ILogger _logger; private readonly MaintenanceService _maintenanceService; - private readonly GalacticAdminService _galacticAdminService; public TriggerMaintenanceFunction( ILogger logger, - MaintenanceService maintenanceService, - GalacticAdminService galacticAdminService) + MaintenanceService maintenanceService) { _logger = logger; _maintenanceService = maintenanceService; - _galacticAdminService = galacticAdminService; } /// @@ -43,34 +39,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { _logger.LogInformation("Manual maintenance trigger requested"); - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated maintenance trigger attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin via GalacticAdminService (Azure Table Storage) + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to trigger maintenance"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } _logger.LogInformation($"Maintenance trigger initiated by Galactic Admin: {userEmail}"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/VersionBlockFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/VersionBlockFunction.cs index efe1e185..56342aef 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Admin/VersionBlockFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Admin/VersionBlockFunction.cs @@ -15,16 +15,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin { private readonly ILogger _logger; private readonly BlockedVersionService _blockedVersionService; - private readonly GalacticAdminService _galacticAdminService; public VersionBlockFunction( ILogger logger, - BlockedVersionService blockedVersionService, - GalacticAdminService galacticAdminService) + BlockedVersionService blockedVersionService) { _logger = logger; _blockedVersionService = blockedVersionService; - _galacticAdminService = galacticAdminService; } /// GET /api/versions/blocked — list all active version block rules @@ -34,12 +31,7 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - - var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var rules = await _blockedVersionService.GetBlockedVersionsAsync(); @@ -63,12 +55,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); string body; using (var reader = new System.IO.StreamReader(req.Body)) @@ -130,12 +118,8 @@ namespace AutopilotMonitor.Functions.Functions.Admin { try { - if (!TenantHelper.IsAuthenticated(req)) - return await UnauthorizedAsync(req); - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userIdentifier = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - return await ForbiddenAsync(req); var versionPattern = Uri.UnescapeDataString(encodedPattern ?? string.Empty); if (string.IsNullOrEmpty(versionPattern)) @@ -164,20 +148,6 @@ namespace AutopilotMonitor.Functions.Functions.Admin // Helpers // ----------------------------------------------------------------------- - private static async Task UnauthorizedAsync(HttpRequestData req) - { - var r = req.CreateResponse(HttpStatusCode.Unauthorized); - await r.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return r; - } - - private static async Task ForbiddenAsync(HttpRequestData req) - { - var r = req.CreateResponse(HttpStatusCode.Forbidden); - await r.WriteAsJsonAsync(new { success = false, message = "Access denied. Only Galactic Admins can manage version blocks." }); - return r; - } - private static async Task BadRequestAsync(HttpRequestData req, string message) { var r = req.CreateResponse(HttpStatusCode.BadRequest); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/CreateBootstrapSessionFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/CreateBootstrapSessionFunction.cs index f32ba024..6f1004bd 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/CreateBootstrapSessionFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/CreateBootstrapSessionFunction.cs @@ -21,20 +21,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap private readonly ILogger _logger; private readonly BootstrapSessionService _bootstrapService; private readonly GalacticAdminService _galacticAdminService; - private readonly TenantAdminsService _tenantAdminsService; private readonly TenantConfigurationService _configService; public CreateBootstrapSessionFunction( ILogger logger, BootstrapSessionService bootstrapService, GalacticAdminService galacticAdminService, - TenantAdminsService tenantAdminsService, TenantConfigurationService configService) { _logger = logger; _bootstrapService = bootstrapService; _galacticAdminService = galacticAdminService; - _tenantAdminsService = tenantAdminsService; _configService = configService; } @@ -44,13 +41,7 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauth = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauth.WriteAsJsonAsync(new { error = "Authentication required" }); - return unauth; - } - + // Authentication + BootstrapManagerOrGA authorization enforced by PolicyEnforcementMiddleware var authenticatedTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); @@ -70,7 +61,7 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap // Use tenant from JWT if not specified in body var tenantId = !string.IsNullOrEmpty(request.TenantId) ? request.TenantId : authenticatedTenantId; - // Tenant boundary check + // Cross-tenant boundary check — only Galactic Admins may operate on other tenants if (!string.Equals(authenticatedTenantId, tenantId, StringComparison.OrdinalIgnoreCase)) { var isGalactic = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); @@ -82,16 +73,6 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap } } - // Admin or Operator with bootstrap permission check - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var canManageBootstrap = await _tenantAdminsService.CanManageBootstrapAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !canManageBootstrap) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Bootstrap token management access required" }); - return forbidden; - } - // Check if bootstrap token feature is enabled for this tenant var tenantConfig = await _configService.GetConfigurationAsync(tenantId); if (!tenantConfig.BootstrapTokenEnabled) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/ListBootstrapSessionsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/ListBootstrapSessionsFunction.cs index 4172f684..66fa5291 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/ListBootstrapSessionsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/ListBootstrapSessionsFunction.cs @@ -20,20 +20,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap private readonly ILogger _logger; private readonly BootstrapSessionService _bootstrapService; private readonly GalacticAdminService _galacticAdminService; - private readonly TenantAdminsService _tenantAdminsService; private readonly TenantConfigurationService _configService; public ListBootstrapSessionsFunction( ILogger logger, BootstrapSessionService bootstrapService, GalacticAdminService galacticAdminService, - TenantAdminsService tenantAdminsService, TenantConfigurationService configService) { _logger = logger; _bootstrapService = bootstrapService; _galacticAdminService = galacticAdminService; - _tenantAdminsService = tenantAdminsService; _configService = configService; } @@ -43,19 +40,13 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauth = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauth.WriteAsJsonAsync(new { error = "Authentication required" }); - return unauth; - } - + // Authentication + BootstrapManagerOrGA authorization enforced by PolicyEnforcementMiddleware var authenticatedTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); var tenantId = req.Query["tenantId"] ?? authenticatedTenantId; - // Tenant boundary check + // Cross-tenant boundary check — only Galactic Admins may operate on other tenants if (!string.Equals(authenticatedTenantId, tenantId, StringComparison.OrdinalIgnoreCase)) { var isGalactic = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); @@ -67,16 +58,6 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap } } - // Admin or Operator with bootstrap permission check - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var canManageBootstrap = await _tenantAdminsService.CanManageBootstrapAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !canManageBootstrap) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Bootstrap token management access required" }); - return forbidden; - } - // Check if bootstrap token feature is enabled for this tenant var tenantConfig = await _configService.GetConfigurationAsync(tenantId); if (!tenantConfig.BootstrapTokenEnabled) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/RevokeBootstrapSessionFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/RevokeBootstrapSessionFunction.cs index 5e0e8af1..94e0ec2d 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/RevokeBootstrapSessionFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Bootstrap/RevokeBootstrapSessionFunction.cs @@ -18,20 +18,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap private readonly ILogger _logger; private readonly BootstrapSessionService _bootstrapService; private readonly GalacticAdminService _galacticAdminService; - private readonly TenantAdminsService _tenantAdminsService; private readonly TenantConfigurationService _configService; public RevokeBootstrapSessionFunction( ILogger logger, BootstrapSessionService bootstrapService, GalacticAdminService galacticAdminService, - TenantAdminsService tenantAdminsService, TenantConfigurationService configService) { _logger = logger; _bootstrapService = bootstrapService; _galacticAdminService = galacticAdminService; - _tenantAdminsService = tenantAdminsService; _configService = configService; } @@ -42,19 +39,13 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauth = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauth.WriteAsJsonAsync(new { error = "Authentication required" }); - return unauth; - } - + // Authentication + BootstrapManagerOrGA authorization enforced by PolicyEnforcementMiddleware var authenticatedTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); var tenantId = req.Query["tenantId"] ?? authenticatedTenantId; - // Tenant boundary check + // Cross-tenant boundary check — only Galactic Admins may operate on other tenants if (!string.Equals(authenticatedTenantId, tenantId, StringComparison.OrdinalIgnoreCase)) { var isGalactic = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); @@ -66,16 +57,6 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap } } - // Admin or Operator with bootstrap permission check - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var canManageBootstrap = await _tenantAdminsService.CanManageBootstrapAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !canManageBootstrap) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Bootstrap token management access required" }); - return forbidden; - } - // Check if bootstrap token feature is enabled for this tenant var tenantConfig = await _configService.GetConfigurationAsync(tenantId); if (!tenantConfig.BootstrapTokenEnabled) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAdminConfigurationFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAdminConfigurationFunction.cs index 78c8667f..dfe4cbff 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAdminConfigurationFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAdminConfigurationFunction.cs @@ -13,16 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Config { private readonly ILogger _logger; private readonly AdminConfigurationService _adminConfigService; - private readonly GalacticAdminService _galacticAdminService; public GetAdminConfigurationFunction( ILogger logger, - AdminConfigurationService adminConfigService, - GalacticAdminService galacticAdminService) + AdminConfigurationService adminConfigService) { _logger = logger; _adminConfigService = adminConfigService; - _galacticAdminService = galacticAdminService; } [Function("GetAdminConfiguration")] @@ -31,36 +28,9 @@ namespace AutopilotMonitor.Functions.Functions.Config { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetAdminConfiguration attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Get tenant ID and user identifier from JWT token - string tenantId = TenantHelper.GetTenantId(req); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Validate Galactic Admin role - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - { - _logger.LogWarning($"Non-Galactic Admin user {userIdentifier} from tenant {tenantId} attempted to access admin configuration"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Only Galactic Admins can access global configuration." - }); - return forbiddenResponse; - } - _logger.LogInformation($"GetAdminConfiguration by Galactic Admin user {userIdentifier}"); var config = await _adminConfigService.GetConfigurationAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAllTenantConfigurationsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAllTenantConfigurationsFunction.cs index daf0a32b..0d355d35 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAllTenantConfigurationsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetAllTenantConfigurationsFunction.cs @@ -13,16 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Config { private readonly ILogger _logger; private readonly TenantConfigurationService _configService; - private readonly GalacticAdminService _galacticAdminService; public GetAllTenantConfigurationsFunction( ILogger logger, - TenantConfigurationService configService, - GalacticAdminService galacticAdminService) + TenantConfigurationService configService) { _logger = logger; _configService = configService; - _galacticAdminService = galacticAdminService; } [Function("GetAllTenantConfigurations")] @@ -31,37 +28,9 @@ namespace AutopilotMonitor.Functions.Functions.Config { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetAllTenantConfigurations attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Get user identifier + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Check if user is Galactic Admin - bool isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"User {userIdentifier} attempted to access all tenant configurations without Galactic Admin privileges"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. This endpoint requires Galactic Admin privileges." - }); - return forbiddenResponse; - } - _logger.LogInformation($"GetAllTenantConfigurations by Galactic Admin {userIdentifier}"); var configurations = await _configService.GetAllConfigurationsAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetTenantConfigurationFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetTenantConfigurationFunction.cs index 3dca215f..604ce5c1 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetTenantConfigurationFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/GetTenantConfigurationFunction.cs @@ -13,13 +13,16 @@ namespace AutopilotMonitor.Functions.Functions.Config { private readonly ILogger _logger; private readonly TenantConfigurationService _configService; + private readonly GalacticAdminService _galacticAdminService; public GetTenantConfigurationFunction( ILogger logger, - TenantConfigurationService configService) + TenantConfigurationService configService, + GalacticAdminService galacticAdminService) { _logger = logger; _configService = configService; + _galacticAdminService = galacticAdminService; } [Function("GetTenantConfiguration")] @@ -29,34 +32,26 @@ namespace AutopilotMonitor.Functions.Functions.Config { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"Unauthenticated GetTenantConfiguration attempt for tenant {tenantId}"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Get tenant ID from JWT token and validate access + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware string authenticatedTenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Validate tenant access: User can only access their own tenant's configuration - if (authenticatedTenantId != tenantId) + // Validate tenant access: cross-tenant only for Galactic Admins + if (!string.Equals(authenticatedTenantId, tenantId, StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning($"User {userIdentifier} from tenant {authenticatedTenantId} attempted to access configuration for tenant {tenantId}"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new + var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); + if (!isGalacticAdmin) { - success = false, - message = "Access denied. You can only access your own tenant's configuration." - }); - return forbiddenResponse; + _logger.LogWarning("User {User} from tenant {AuthTenant} attempted to access configuration for tenant {TargetTenant}", + userIdentifier, authenticatedTenantId, tenantId); + var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); + await forbiddenResponse.WriteAsJsonAsync(new + { + success = false, + message = "Access denied. You can only access your own tenant's configuration." + }); + return forbiddenResponse; + } } _logger.LogInformation($"GetTenantConfiguration: {tenantId} by user {userIdentifier}"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateAdminConfigurationFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateAdminConfigurationFunction.cs index 27ed68dd..48a46b4e 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateAdminConfigurationFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateAdminConfigurationFunction.cs @@ -16,16 +16,13 @@ namespace AutopilotMonitor.Functions.Functions.Config { private readonly ILogger _logger; private readonly AdminConfigurationService _adminConfigService; - private readonly GalacticAdminService _galacticAdminService; public UpdateAdminConfigurationFunction( ILogger logger, - AdminConfigurationService adminConfigService, - GalacticAdminService galacticAdminService) + AdminConfigurationService adminConfigService) { _logger = logger; _adminConfigService = adminConfigService; - _galacticAdminService = galacticAdminService; } [Function("UpdateAdminConfiguration")] @@ -34,36 +31,9 @@ namespace AutopilotMonitor.Functions.Functions.Config { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated UpdateAdminConfiguration attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Get tenant ID and user identifier from JWT token - string tenantId = TenantHelper.GetTenantId(req); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Validate Galactic Admin role - if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) - { - _logger.LogWarning($"Non-Galactic Admin user {userIdentifier} from tenant {tenantId} attempted to update admin configuration"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Only Galactic Admins can modify global configuration." - }); - return forbiddenResponse; - } - _logger.LogInformation($"UpdateAdminConfiguration by Galactic Admin user {userIdentifier}"); // Parse request body diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateTenantConfigurationFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateTenantConfigurationFunction.cs index c2073921..d9175207 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateTenantConfigurationFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Config/UpdateTenantConfigurationFunction.cs @@ -16,18 +16,15 @@ namespace AutopilotMonitor.Functions.Functions.Config { private readonly ILogger _logger; private readonly TenantConfigurationService _configService; - private readonly TenantAdminsService _tenantAdminsService; private readonly GalacticAdminService _galacticAdminService; public UpdateTenantConfigurationFunction( ILogger logger, TenantConfigurationService configService, - TenantAdminsService tenantAdminsService, GalacticAdminService galacticAdminService) { _logger = logger; _configService = configService; - _tenantAdminsService = tenantAdminsService; _galacticAdminService = galacticAdminService; } @@ -38,27 +35,18 @@ namespace AutopilotMonitor.Functions.Functions.Config { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"Unauthenticated UpdateTenantConfiguration attempt for tenant {tenantId}"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Get tenant ID from JWT token and validate access + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware string authenticatedTenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Validate tenant access: User can only update their own tenant's configuration - if (authenticatedTenantId != tenantId) + // GA check needed for: cross-tenant bypass + protected field filtering + var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); + + // Validate tenant access: cross-tenant only for Galactic Admins + if (!isGalacticAdmin && !string.Equals(authenticatedTenantId, tenantId, StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning($"User {userIdentifier} from tenant {authenticatedTenantId} attempted to update configuration for tenant {tenantId}"); + _logger.LogWarning("User {User} from tenant {AuthTenant} attempted to update configuration for tenant {TargetTenant}", + userIdentifier, authenticatedTenantId, tenantId); var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); await forbiddenResponse.WriteAsJsonAsync(new { @@ -68,22 +56,7 @@ namespace AutopilotMonitor.Functions.Functions.Config return forbiddenResponse; } - // Require Tenant Admin or Galactic Admin for configuration changes - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - _logger.LogWarning($"Non-admin user {userIdentifier} attempted to update configuration for tenant {tenantId}"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Tenant Admin or Galactic Admin role required." - }); - return forbiddenResponse; - } - - _logger.LogInformation($"UpdateTenantConfiguration: {tenantId} by user {userIdentifier}"); + _logger.LogInformation("UpdateTenantConfiguration: {TenantId} by user {User}", tenantId, userIdentifier); // Parse request body if (req.Headers.TryGetValues("Content-Length", out var clValues) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Diagnostics/DiagnosticsDownloadFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Diagnostics/DiagnosticsDownloadFunction.cs index 335ef51f..1d39d30f 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Diagnostics/DiagnosticsDownloadFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Diagnostics/DiagnosticsDownloadFunction.cs @@ -34,14 +34,7 @@ namespace AutopilotMonitor.Functions.Functions.Diagnostics { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var userTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/AuthFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/AuthFunction.cs index 718bdbe6..9fa9c0c9 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/AuthFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/AuthFunction.cs @@ -217,21 +217,7 @@ public class AuthFunction [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "auth/galactic-admins")] HttpRequestData req, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } - - var upn = principal.GetUserPrincipalName(); - var isAdmin = await _galacticAdminService.IsGalacticAdminAsync(upn); - - if (!isAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { error = "Only Galactic Admins can access this endpoint" }); - return forbiddenResponse; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var admins = await _galacticAdminService.GetAllGalacticAdminsAsync(); @@ -250,21 +236,9 @@ public class AuthFunction [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "auth/galactic-admins")] HttpRequestData req, FunctionContext context) { + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } - - var currentUpn = principal.GetUserPrincipalName(); - var isAdmin = await _galacticAdminService.IsGalacticAdminAsync(currentUpn); - - if (!isAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { error = "Only Galactic Admins can add other admins" }); - return forbiddenResponse; - } + var currentUpn = principal?.GetUserPrincipalName(); // Parse request body var body = await req.ReadFromJsonAsync(); @@ -295,21 +269,9 @@ public class AuthFunction string upn, FunctionContext context) { + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var principal = context.GetUser(); - if (principal == null) - { - return req.CreateResponse(HttpStatusCode.Unauthorized); - } - - var currentUpn = principal.GetUserPrincipalName(); - var isAdmin = await _galacticAdminService.IsGalacticAdminAsync(currentUpn); - - if (!isAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { error = "Only Galactic Admins can remove other admins" }); - return forbiddenResponse; - } + var currentUpn = principal?.GetUserPrincipalName(); // Prevent self-removal if (upn.Equals(currentUpn, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/HealthCheckFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/HealthCheckFunction.cs index 9e22b7af..896df03f 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/HealthCheckFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/HealthCheckFunction.cs @@ -1,6 +1,4 @@ using System.Net; -using AutopilotMonitor.Functions.Extensions; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -12,16 +10,13 @@ namespace AutopilotMonitor.Functions.Functions.Infrastructure { private readonly ILogger _logger; private readonly HealthCheckService _healthCheckService; - private readonly GalacticAdminService _galacticAdminService; public HealthCheckFunction( ILogger logger, - HealthCheckService healthCheckService, - GalacticAdminService galacticAdminService) + HealthCheckService healthCheckService) { _logger = logger; _healthCheckService = healthCheckService; - _galacticAdminService = galacticAdminService; } /// @@ -57,36 +52,7 @@ namespace AutopilotMonitor.Functions.Functions.Infrastructure { _logger.LogInformation("Detailed health check requested"); - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated detailed health check attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin via GalacticAdminService (Azure Table Storage) - var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to access detailed health check"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } - - _logger.LogInformation($"Detailed health check accessed by Galactic Admin: {userEmail}"); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware // Perform comprehensive health checks var healthCheckResult = await _healthCheckService.PerformAllChecksAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRAddToGroupFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRAddToGroupFunction.cs index 9190224d..f7e8bd04 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRAddToGroupFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRAddToGroupFunction.cs @@ -28,14 +28,7 @@ namespace AutopilotMonitor.Functions.Functions.Infrastructure { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated AddToGroup attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return new AddToGroupOutput { HttpResponse = unauthorizedResponse }; - } + // Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware // Parse request if (req.Headers.TryGetValues("Content-Length", out var clValues) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRRemoveFromGroupFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRRemoveFromGroupFunction.cs index 553fb225..77244fd3 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRRemoveFromGroupFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Infrastructure/SignalRRemoveFromGroupFunction.cs @@ -28,14 +28,7 @@ namespace AutopilotMonitor.Functions.Functions.Infrastructure { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated RemoveFromGroup attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return new RemoveFromGroupOutput { HttpResponse = unauthorizedResponse }; - } + // Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware // Parse request if (req.Headers.TryGetValues("Content-Length", out var clValues) diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetAppMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetAppMetricsFunction.cs index ff7bc790..0f27bc37 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetAppMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetAppMetricsFunction.cs @@ -24,14 +24,7 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var tenantId = TenantHelper.GetTenantId(req); _logger.LogInformation($"Fetching app metrics for tenant {tenantId}"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticAppMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticAppMetricsFunction.cs index a112005f..14e2708e 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticAppMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticAppMetricsFunction.cs @@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly GalacticAdminService _galacticAdminService; public GetGalacticAppMetricsFunction( ILogger logger, - TableStorageService storageService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _galacticAdminService = galacticAdminService; } [Function("GetGalacticAppMetrics")] @@ -29,22 +26,8 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." }); - return forbiddenResponse; - } _logger.LogInformation($"Fetching galactic app metrics (User: {userEmail})"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticGeographicMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticGeographicMetricsFunction.cs index 0325bffe..b308f105 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticGeographicMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticGeographicMetricsFunction.cs @@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly GalacticAdminService _galacticAdminService; public GetGalacticGeographicMetricsFunction( ILogger logger, - TableStorageService storageService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _galacticAdminService = galacticAdminService; } [Function("GetGalacticGeographicMetrics")] @@ -29,22 +26,8 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." }); - return forbiddenResponse; - } _logger.LogInformation("Fetching galactic geographic metrics (User: {UserEmail})", userEmail); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticPlatformMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticPlatformMetricsFunction.cs index b4f1980a..81baa5c5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticPlatformMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGalacticPlatformMetricsFunction.cs @@ -1,5 +1,4 @@ using System.Net; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -15,16 +14,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { private readonly ILogger _logger; private readonly PlatformMetricsService _platformMetricsService; - private readonly GalacticAdminService _galacticAdminService; public GetGalacticPlatformMetricsFunction( ILogger logger, - PlatformMetricsService platformMetricsService, - GalacticAdminService galacticAdminService) + PlatformMetricsService platformMetricsService) { _logger = logger; _platformMetricsService = platformMetricsService; - _galacticAdminService = galacticAdminService; } [Function("GetGalacticPlatformMetrics")] @@ -35,36 +31,7 @@ namespace AutopilotMonitor.Functions.Functions.Metrics try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated platform metrics attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin - var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to access platform metrics"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } - - _logger.LogInformation($"Platform agent metrics accessed by Galactic Admin: {userEmail}"); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var metrics = await _platformMetricsService.ComputePlatformMetricsAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicMetricsFunction.cs index 64bff5e4..0dc58279 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/GetGeographicMetricsFunction.cs @@ -27,13 +27,7 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var tenantId = TenantHelper.GetTenantId(req); _logger.LogInformation("Fetching geographic metrics for tenant {TenantId}", tenantId); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/PlatformUsageMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/PlatformUsageMetricsFunction.cs index 963088ee..95d1c9ca 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/PlatformUsageMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/PlatformUsageMetricsFunction.cs @@ -1,5 +1,4 @@ using System.Net; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -14,16 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics { private readonly ILogger _logger; private readonly UsageMetricsService _usageMetricsService; - private readonly GalacticAdminService _galacticAdminService; public PlatformUsageMetricsFunction( ILogger logger, - UsageMetricsService usageMetricsService, - GalacticAdminService galacticAdminService) + UsageMetricsService usageMetricsService) { _logger = logger; _usageMetricsService = usageMetricsService; - _galacticAdminService = galacticAdminService; } /// @@ -39,36 +35,7 @@ namespace AutopilotMonitor.Functions.Functions.Metrics try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated platform usage metrics attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin via GalacticAdminService (Azure Table Storage) - var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to access platform usage metrics"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } - - _logger.LogInformation($"Platform usage metrics accessed by Galactic Admin: {userEmail}"); + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var metrics = await _usageMetricsService.ComputeUsageMetricsAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/UsageMetricsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/UsageMetricsFunction.cs index c8f9a1fa..ca3698cd 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/UsageMetricsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Metrics/UsageMetricsFunction.cs @@ -36,19 +36,7 @@ namespace AutopilotMonitor.Functions.Functions.Metrics try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated UsageMetrics attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware string tenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Progress/ProgressPortalFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Progress/ProgressPortalFunction.cs index 2c6aedd1..1337bbf0 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Progress/ProgressPortalFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Progress/ProgressPortalFunction.cs @@ -41,19 +41,7 @@ public class ProgressPortalFunction try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token.", - count = 0, - sessions = Array.Empty() - }); - return unauthorizedResponse; - } - + // Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware var tenantId = TenantHelper.GetTenantId(req); _logger.LogInformation("ProgressGetSessions: Fetching sessions for tenant {TenantId}", tenantId); @@ -106,20 +94,7 @@ public class ProgressPortalFunction try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token.", - sessionId = sessionId, - count = 0, - events = Array.Empty() - }); - return unauthorizedResponse; - } - + // Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware var userTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportDownloadUrlFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportDownloadUrlFunction.cs index fbfd8ddd..769fa8b1 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportDownloadUrlFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportDownloadUrlFunction.cs @@ -1,7 +1,6 @@ using System.Net; using System.Web; using Azure.Storage.Blobs; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -17,17 +16,14 @@ namespace AutopilotMonitor.Functions.Functions.Reports public class GetSessionReportDownloadUrlFunction { private readonly ILogger _logger; - private readonly GalacticAdminService _galacticAdminService; private readonly string _blobConnectionString; private const string ContainerName = "session-reports"; public GetSessionReportDownloadUrlFunction( ILogger logger, - GalacticAdminService galacticAdminService, IConfiguration configuration) { _logger = logger; - _galacticAdminService = galacticAdminService; _blobConnectionString = configuration["AzureBlobStorageConnectionString"] ?? throw new InvalidOperationException("AzureBlobStorageConnectionString is not configured"); } @@ -38,21 +34,7 @@ namespace AutopilotMonitor.Functions.Functions.Reports { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - if (!isGalacticAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." }); - return forbiddenResponse; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var query = HttpUtility.ParseQueryString(req.Url.Query); var blobName = query["blobName"]; diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportsFunction.cs index 73d7843a..e100ab1a 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/GetSessionReportsFunction.cs @@ -1,5 +1,4 @@ using System.Net; -using AutopilotMonitor.Functions.Helpers; using AutopilotMonitor.Functions.Services; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; @@ -11,16 +10,13 @@ namespace AutopilotMonitor.Functions.Functions.Reports { private readonly ILogger _logger; private readonly SessionReportService _sessionReportService; - private readonly GalacticAdminService _galacticAdminService; public GetSessionReportsFunction( ILogger logger, - SessionReportService sessionReportService, - GalacticAdminService galacticAdminService) + SessionReportService sessionReportService) { _logger = logger; _sessionReportService = sessionReportService; - _galacticAdminService = galacticAdminService; } [Function("GetSessionReports")] @@ -31,32 +27,7 @@ namespace AutopilotMonitor.Functions.Functions.Reports try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required." - }); - return unauthorizedResponse; - } - - string userIdentifier = TenantHelper.GetUserIdentifier(req); - - // Require Galactic Admin only (this is admin-config data) - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - if (!isGalacticAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var reports = await _sessionReportService.GetAllReportsAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs index bbccadb8..42130078 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs @@ -13,7 +13,6 @@ namespace AutopilotMonitor.Functions.Functions.Reports private readonly ILogger _logger; private readonly SessionReportService _sessionReportService; private readonly TableStorageService _storageService; - private readonly TenantAdminsService _tenantAdminsService; private readonly GalacticAdminService _galacticAdminService; private readonly TelegramNotificationService _telegramNotificationService; @@ -21,14 +20,12 @@ namespace AutopilotMonitor.Functions.Functions.Reports ILogger logger, SessionReportService sessionReportService, TableStorageService storageService, - TenantAdminsService tenantAdminsService, GalacticAdminService galacticAdminService, TelegramNotificationService telegramNotificationService) { _logger = logger; _sessionReportService = sessionReportService; _storageService = storageService; - _tenantAdminsService = tenantAdminsService; _galacticAdminService = galacticAdminService; _telegramNotificationService = telegramNotificationService; } @@ -42,36 +39,12 @@ namespace AutopilotMonitor.Functions.Functions.Reports try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated session report attempt for session {SessionId}", sessionId); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware string tenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Require Tenant Admin or Galactic Admin + // Still needed: GA check for audit log skip logic var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - _logger.LogWarning("Non-admin user {UserIdentifier} attempted to submit session report for {SessionId}", userIdentifier, sessionId); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Tenant Admin or Galactic Admin role required." - }); - return forbiddenResponse; - } // Parse request body var request = await req.ReadFromJsonAsync(); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/UpdateSessionReportNoteFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/UpdateSessionReportNoteFunction.cs index 64fd637c..62335c79 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/UpdateSessionReportNoteFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/UpdateSessionReportNoteFunction.cs @@ -16,16 +16,13 @@ namespace AutopilotMonitor.Functions.Functions.Reports { private readonly ILogger _logger; private readonly SessionReportService _sessionReportService; - private readonly GalacticAdminService _galacticAdminService; public UpdateSessionReportNoteFunction( ILogger logger, - SessionReportService sessionReportService, - GalacticAdminService galacticAdminService) + SessionReportService sessionReportService) { _logger = logger; _sessionReportService = sessionReportService; - _galacticAdminService = galacticAdminService; } [Function("UpdateSessionReportNote")] @@ -35,21 +32,8 @@ namespace AutopilotMonitor.Functions.Functions.Reports { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); - return unauthorizedResponse; - } - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - if (!isGalacticAdmin) - { - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new { success = false, message = "Access denied. Galactic Admin role required." }); - return forbiddenResponse; - } if (string.IsNullOrEmpty(reportId)) { diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/AnalyzeRulesFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/AnalyzeRulesFunction.cs index 5cec6b4e..2bbe7611 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/AnalyzeRulesFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/AnalyzeRulesFunction.cs @@ -16,32 +16,21 @@ namespace AutopilotMonitor.Functions.Functions.Rules { private readonly ILogger _logger; private readonly AnalyzeRuleService _ruleService; - private readonly TenantAdminsService _tenantAdminsService; - private readonly GalacticAdminService _galacticAdminService; public AnalyzeRulesFunction( ILogger logger, - AnalyzeRuleService ruleService, - TenantAdminsService tenantAdminsService, - GalacticAdminService galacticAdminService) + AnalyzeRuleService ruleService) { _logger = logger; _ruleService = ruleService; - _tenantAdminsService = tenantAdminsService; - _galacticAdminService = galacticAdminService; } [Function("GetAnalyzeRules")] public async Task GetRules( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "rules/analyze")] HttpRequestData req) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); var rules = await _ruleService.GetAllRulesForTenantAsync(tenantId); @@ -54,24 +43,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules public async Task CreateRule( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "rules/analyze")] HttpRequestData req) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); if (req.Headers.TryGetValues("Content-Length", out var clValues) && long.TryParse(clValues.FirstOrDefault(), out var contentLength) @@ -103,24 +76,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "rules/analyze/{ruleId}")] HttpRequestData req, string ruleId) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); if (req.Headers.TryGetValues("Content-Length", out var clValues2) && long.TryParse(clValues2.FirstOrDefault(), out var contentLength2) @@ -153,24 +110,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "rules/analyze/{ruleId}")] HttpRequestData req, string ruleId) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); // Load the rule to determine its type (built-in/community vs. custom) var rules = await _ruleService.GetAllRulesForTenantAsync(tenantId); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GatherRulesFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GatherRulesFunction.cs index 188054a8..9ef52804 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GatherRulesFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GatherRulesFunction.cs @@ -16,32 +16,21 @@ namespace AutopilotMonitor.Functions.Functions.Rules { private readonly ILogger _logger; private readonly GatherRuleService _ruleService; - private readonly TenantAdminsService _tenantAdminsService; - private readonly GalacticAdminService _galacticAdminService; public GatherRulesFunction( ILogger logger, - GatherRuleService ruleService, - TenantAdminsService tenantAdminsService, - GalacticAdminService galacticAdminService) + GatherRuleService ruleService) { _logger = logger; _ruleService = ruleService; - _tenantAdminsService = tenantAdminsService; - _galacticAdminService = galacticAdminService; } [Function("GetGatherRules")] public async Task GetRules( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "rules/gather")] HttpRequestData req) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); var rules = await _ruleService.GetAllRulesForTenantAsync(tenantId); @@ -54,24 +43,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules public async Task CreateRule( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "rules/gather")] HttpRequestData req) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); if (req.Headers.TryGetValues("Content-Length", out var clValues) && long.TryParse(clValues.FirstOrDefault(), out var contentLength) @@ -103,24 +76,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "rules/gather/{ruleId}")] HttpRequestData req, string ruleId) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); if (req.Headers.TryGetValues("Content-Length", out var clValues2) && long.TryParse(clValues2.FirstOrDefault(), out var contentLength2) @@ -153,24 +110,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "rules/gather/{ruleId}")] HttpRequestData req, string ruleId) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - - // Require Tenant Admin or Galactic Admin for rule modifications - var userIdentifier = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Access denied. Tenant Admin or Galactic Admin role required." }); - return forbidden; - } + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); // Load the rule to determine its type (built-in/community vs. custom) var rules = await _ruleService.GetAllRulesForTenantAsync(tenantId); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GetRuleResultsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GetRuleResultsFunction.cs index 54757f97..e254fa80 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GetRuleResultsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/GetRuleResultsFunction.cs @@ -37,13 +37,7 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "sessions/{sessionId}/analysis")] HttpRequestData req, string sessionId) { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var userTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/ImeLogPatternsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/ImeLogPatternsFunction.cs index 184d89a4..f94ca251 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/ImeLogPatternsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/ImeLogPatternsFunction.cs @@ -17,26 +17,19 @@ namespace AutopilotMonitor.Functions.Functions.Rules { private readonly ILogger _logger; private readonly ImeLogPatternService _patternService; - private readonly GalacticAdminService _galacticAdminService; - public ImeLogPatternsFunction(ILogger logger, ImeLogPatternService patternService, GalacticAdminService galacticAdminService) + public ImeLogPatternsFunction(ILogger logger, ImeLogPatternService patternService) { _logger = logger; _patternService = patternService; - _galacticAdminService = galacticAdminService; } [Function("GetImeLogPatterns")] public async Task GetPatterns( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "rules/ime-log-patterns")] HttpRequestData req) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware + var tenantId = TenantHelper.GetTenantId(req); var patterns = await _patternService.GetAllPatternsForTenantAsync(tenantId); @@ -50,29 +43,14 @@ namespace AutopilotMonitor.Functions.Functions.Rules [HttpTrigger(AuthorizationLevel.Anonymous, "put", Route = "rules/ime-log-patterns/{patternId}")] HttpRequestData req, string patternId) { - var tenantId = TenantHelper.IsAuthenticated(req) ? TenantHelper.GetTenantId(req) : null; - if (string.IsNullOrEmpty(tenantId)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware - // Phase 1: Only global edits by Galactic Admins + // Phase 1: Only global edits var globalEdit = req.Url.Query.Contains("global=true", StringComparison.OrdinalIgnoreCase); if (!globalEdit) { var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "IME log pattern edits require ?global=true and Galactic Admin privileges" }); - return forbidden; - } - - var upn = TenantHelper.GetUserIdentifier(req); - var isGalactic = await _galacticAdminService.IsGalacticAdminAsync(upn); - if (!isGalactic) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Galactic Admin privileges required" }); + await forbidden.WriteAsJsonAsync(new { success = false, message = "IME log pattern edits require ?global=true" }); return forbidden; } @@ -108,20 +86,8 @@ namespace AutopilotMonitor.Functions.Functions.Rules { try { - if (!TenantHelper.IsAuthenticated(req)) - { - var unauthorized = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorized.WriteAsJsonAsync(new { success = false, message = "Authentication required" }); - return unauthorized; - } - + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var upn = TenantHelper.GetUserIdentifier(req); - if (!await _galacticAdminService.IsGalacticAdminAsync(upn)) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { success = false, message = "Galactic Admin privileges required" }); - return forbidden; - } _logger.LogInformation($"Reseed IME log patterns triggered by Galactic Admin {upn}"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/PreviewWhitelistFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/PreviewWhitelistFunction.cs index 2df8e8b6..59dbb8f5 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Rules/PreviewWhitelistFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Rules/PreviewWhitelistFunction.cs @@ -17,20 +17,17 @@ public class PreviewWhitelistFunction { private readonly ILogger _logger; private readonly PreviewWhitelistService _previewWhitelistService; - private readonly GalacticAdminService _galacticAdminService; private readonly TenantConfigurationService _tenantConfigurationService; private readonly TenantAdminsService _tenantAdminsService; public PreviewWhitelistFunction( ILogger logger, PreviewWhitelistService previewWhitelistService, - GalacticAdminService galacticAdminService, TenantConfigurationService tenantConfigurationService, TenantAdminsService tenantAdminsService) { _logger = logger; _previewWhitelistService = previewWhitelistService; - _galacticAdminService = galacticAdminService; _tenantConfigurationService = tenantConfigurationService; _tenantAdminsService = tenantAdminsService; } @@ -45,17 +42,7 @@ public class PreviewWhitelistFunction [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "preview/whitelist")] HttpRequestData req, FunctionContext context) { - var principal = context.GetUser(); - if (principal == null) - return req.CreateResponse(HttpStatusCode.Unauthorized); - - var upn = principal.GetUserPrincipalName(); - if (!await _galacticAdminService.IsGalacticAdminAsync(upn)) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Only Galactic Admins can access this endpoint" }); - return forbidden; - } + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var approved = await _previewWhitelistService.GetAllApprovedAsync(); @@ -75,17 +62,9 @@ public class PreviewWhitelistFunction string tenantId, FunctionContext context) { + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var principal = context.GetUser(); - if (principal == null) - return req.CreateResponse(HttpStatusCode.Unauthorized); - - var upn = principal.GetUserPrincipalName(); - if (!await _galacticAdminService.IsGalacticAdminAsync(upn)) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Only Galactic Admins can approve tenants" }); - return forbidden; - } + var upn = principal?.GetUserPrincipalName(); if (string.IsNullOrWhiteSpace(tenantId)) { @@ -156,17 +135,9 @@ public class PreviewWhitelistFunction string tenantId, FunctionContext context) { + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var principal = context.GetUser(); - if (principal == null) - return req.CreateResponse(HttpStatusCode.Unauthorized); - - var upn = principal.GetUserPrincipalName(); - if (!await _galacticAdminService.IsGalacticAdminAsync(upn)) - { - var forbidden = req.CreateResponse(HttpStatusCode.Forbidden); - await forbidden.WriteAsJsonAsync(new { error = "Only Galactic Admins can revoke tenant access" }); - return forbidden; - } + var upn = principal?.GetUserPrincipalName(); await _previewWhitelistService.RevokeAsync(tenantId); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/DeleteSessionFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/DeleteSessionFunction.cs index 5f837308..9f239f4d 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/DeleteSessionFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/DeleteSessionFunction.cs @@ -11,19 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly TenantAdminsService _tenantAdminsService; - private readonly GalacticAdminService _galacticAdminService; public DeleteSessionFunction( ILogger logger, - TableStorageService storageService, - TenantAdminsService tenantAdminsService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _tenantAdminsService = tenantAdminsService; - _galacticAdminService = galacticAdminService; } [Function("DeleteSession")] @@ -35,37 +29,10 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"Unauthenticated DeleteSession attempt for session {sessionId}"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware var tenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); - // Require Tenant Admin or Galactic Admin for destructive operations - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - _logger.LogWarning($"Non-admin user {userIdentifier} attempted to delete session {sessionId}"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Tenant Admin or Galactic Admin role required." - }); - return forbiddenResponse; - } - _logger.LogInformation($"Deleting session {sessionId} for tenant {tenantId} by user {userIdentifier}"); // Delete all related data for this session diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetAllSessionsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetAllSessionsFunction.cs index ad0a9363..afe6df53 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetAllSessionsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetAllSessionsFunction.cs @@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly GalacticAdminService _galacticAdminService; public GetAllSessionsFunction( ILogger logger, - TableStorageService storageService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _galacticAdminService = galacticAdminService; } [Function("GetAllSessions")] @@ -31,34 +28,8 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - // Validate authentication and Galactic Admin role - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetAllSessions attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - - // Check if user is Galactic Admin via GalacticAdminService (Azure Table Storage) + // Authentication + GalacticAdminOnly authorization enforced by PolicyEnforcementMiddleware var userEmail = TenantHelper.GetUserIdentifier(req); - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userEmail); - - if (!isGalacticAdmin) - { - _logger.LogWarning($"Non-Galactic Admin user {userEmail} attempted to access GetAllSessions"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Galactic Admin role required." - }); - return forbiddenResponse; - } _logger.LogInformation($"Fetching all sessions across all tenants (User: {userEmail})"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionEventsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionEventsFunction.cs index 31ec407c..e4e01f5f 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionEventsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionEventsFunction.cs @@ -41,21 +41,7 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"{sessionPrefix} Unauthenticated GetSessionEvents attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token.", - sessionId = sessionId, - count = 0, - events = Array.Empty() - }); - return unauthorizedResponse; - } + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware // Get user's tenant ID and identifier from JWT var userTenantId = TenantHelper.GetTenantId(req); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionFunction.cs index 077b85ba..2865d1ba 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionFunction.cs @@ -44,18 +44,7 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"{sessionPrefix} Unauthenticated GetSession attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var userTenantId = TenantHelper.GetTenantId(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); var query = HttpUtility.ParseQueryString(req.Url.Query); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionsFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionsFunction.cs index 2b7c513a..a66a9894 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionsFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/GetSessionsFunction.cs @@ -26,21 +26,7 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning("Unauthenticated GetSessions attempt"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token.", - count = 0, - sessions = Array.Empty() - }); - return unauthorizedResponse; - } - + // Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware var tenantId = TenantHelper.GetTenantId(req); _logger.LogInformation($"Fetching sessions for tenant {tenantId}"); diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/MarkSessionFailedFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/MarkSessionFailedFunction.cs index 279db936..fa40e680 100644 --- a/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/MarkSessionFailedFunction.cs +++ b/src/Backend/AutopilotMonitor.Functions/Functions/Sessions/MarkSessionFailedFunction.cs @@ -13,19 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions { private readonly ILogger _logger; private readonly TableStorageService _storageService; - private readonly TenantAdminsService _tenantAdminsService; - private readonly GalacticAdminService _galacticAdminService; public MarkSessionFailedFunction( ILogger logger, - TableStorageService storageService, - TenantAdminsService tenantAdminsService, - GalacticAdminService galacticAdminService) + TableStorageService storageService) { _logger = logger; _storageService = storageService; - _tenantAdminsService = tenantAdminsService; - _galacticAdminService = galacticAdminService; } [Function("MarkSessionFailed")] @@ -37,37 +31,10 @@ namespace AutopilotMonitor.Functions.Functions.Sessions try { - // Validate authentication - if (!TenantHelper.IsAuthenticated(req)) - { - _logger.LogWarning($"Unauthenticated MarkSessionFailed attempt for session {sessionId}"); - var unauthorizedResponse = req.CreateResponse(HttpStatusCode.Unauthorized); - await unauthorizedResponse.WriteAsJsonAsync(new - { - success = false, - message = "Authentication required. Please provide a valid JWT token." - }); - return new MarkSessionFailedOutput { HttpResponse = unauthorizedResponse }; - } - + // Authentication + TenantAdminOrGA authorization enforced by PolicyEnforcementMiddleware string tenantId = TenantHelper.GetTenantId(req); string userIdentifier = TenantHelper.GetUserIdentifier(req); - // Require Tenant Admin or Galactic Admin for status modifications - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(userIdentifier); - var isTenantAdmin = await _tenantAdminsService.IsTenantAdminAsync(tenantId, userIdentifier); - if (!isGalacticAdmin && !isTenantAdmin) - { - _logger.LogWarning($"Non-admin user {userIdentifier} attempted to mark session {sessionId} as failed"); - var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden); - await forbiddenResponse.WriteAsJsonAsync(new - { - success = false, - message = "Access denied. Tenant Admin or Galactic Admin role required." - }); - return new MarkSessionFailedOutput { HttpResponse = forbiddenResponse }; - } - _logger.LogInformation($"Marking session {sessionId} as failed for tenant {tenantId} by user {userIdentifier}"); // Update session status to Failed with manual failure reason diff --git a/src/Backend/AutopilotMonitor.Functions/Helpers/TenantHelper.cs b/src/Backend/AutopilotMonitor.Functions/Helpers/TenantHelper.cs index 80251654..1621b006 100644 --- a/src/Backend/AutopilotMonitor.Functions/Helpers/TenantHelper.cs +++ b/src/Backend/AutopilotMonitor.Functions/Helpers/TenantHelper.cs @@ -9,27 +9,6 @@ namespace AutopilotMonitor.Functions.Helpers; /// public static class TenantHelper { - /// - /// Checks if the request has an authenticated user - /// Azure Functions Isolated Worker compatible - /// - /// The HTTP request - /// True if user is authenticated, false otherwise - public static bool IsAuthenticated(HttpRequestData req) - { - // Azure Functions Isolated Worker: Try FunctionContext.Items first - // This is set by AuthenticationMiddleware and is more reliable than httpContext.User - if (req.FunctionContext.Items.TryGetValue("ClaimsPrincipal", out var principalObj) - && principalObj is ClaimsPrincipal principal) - { - return principal.Identity?.IsAuthenticated == true; - } - - // Fallback to HTTP context (may not work reliably in isolated worker) - var httpContext = req.FunctionContext.GetHttpContext(); - return httpContext?.User?.Identity?.IsAuthenticated == true; - } - /// /// Extracts the tenant ID from the authenticated user's JWT token claims. /// Uses the Azure AD tenant ID claim which identifies which customer/organization owns the data. diff --git a/src/Backend/AutopilotMonitor.Functions/Middleware/MemberAuthorizationMiddleware.cs b/src/Backend/AutopilotMonitor.Functions/Middleware/MemberAuthorizationMiddleware.cs deleted file mode 100644 index 505c4781..00000000 --- a/src/Backend/AutopilotMonitor.Functions/Middleware/MemberAuthorizationMiddleware.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System.Net; -using System.Security.Claims; -using AutopilotMonitor.Functions.Extensions; -using AutopilotMonitor.Functions.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Middleware; -using Microsoft.Extensions.Logging; - -namespace AutopilotMonitor.Functions.Middleware; - -/// -/// Middleware that enforces tenant membership (Admin or Operator role) on protected routes. -/// Runs after AuthenticationMiddleware. Non-members receive 403 and can only use the Progress Portal. -/// -public class MemberAuthorizationMiddleware : IFunctionsWorkerMiddleware -{ - private readonly ILogger _logger; - private readonly TenantAdminsService _tenantAdminsService; - private readonly GalacticAdminService _galacticAdminService; - - /// - /// Route prefixes that are exempt from the member check. - /// These routes either have their own authorization, use device auth, or are open to any tenant user. - /// - private static readonly string[] _exemptPrefixes = - { - "/api/health", // Anonymous health check - "/api/stats/", // Anonymous platform stats - "/api/agent/", // Device cert auth, no JWT - "/api/bootstrap/", // Bootstrap routes — device routes have no JWT, management routes have inline auth - "/api/auth/", // Must work for any authenticated user (role info) - "/api/progress/", // Progress Portal — open to any tenant user - "/api/realtime/", // SignalR — tenant isolation via group validation - "/api/galactic/", // Galactic Admin routes — have own GA checks - "/api/global/", // Galactic Admin config — has own GA check - "/api/versions/", // Galactic Admin — has own GA check - "/api/preview/", // Preview whitelist — has own GA check - }; - - public MemberAuthorizationMiddleware( - ILogger logger, - TenantAdminsService tenantAdminsService, - GalacticAdminService galacticAdminService) - { - _logger = logger; - _tenantAdminsService = tenantAdminsService; - _galacticAdminService = galacticAdminService; - } - - public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next) - { - var httpContext = context.GetHttpContext(); - if (httpContext == null) - { - // Non-HTTP trigger (e.g. timer, queue) — no auth needed - await next(context); - return; - } - - var requestPath = httpContext.Request.Path.Value ?? string.Empty; - - // Skip exempt routes - if (IsExemptRoute(requestPath)) - { - await next(context); - return; - } - - // Get the ClaimsPrincipal set by AuthenticationMiddleware - if (!context.Items.TryGetValue("ClaimsPrincipal", out var principalObj) - || principalObj is not ClaimsPrincipal principal - || principal.Identity?.IsAuthenticated != true) - { - // Not authenticated — AuthenticationMiddleware will have already returned 401 - // or this is an anonymous route handled above. Just pass through. - await next(context); - return; - } - - var tenantId = principal.GetTenantId(); - var upn = principal.GetUserPrincipalName(); - - if (string.IsNullOrEmpty(tenantId) || string.IsNullOrEmpty(upn)) - { - // Missing claims — let the function handle it - await next(context); - return; - } - - // Galactic Admins bypass member check - var isGalacticAdmin = await _galacticAdminService.IsGalacticAdminAsync(upn); - if (isGalacticAdmin) - { - await next(context); - return; - } - - // Check if user is a tenant member (Admin or Operator) - var isMember = await _tenantAdminsService.IsTenantMemberAsync(tenantId, upn); - if (isMember) - { - await next(context); - return; - } - - // Non-member — block access - _logger.LogWarning("[MemberAuth] Non-member {Upn} (tenant {TenantId}) blocked from {Path}", upn, tenantId, requestPath); - - httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; - httpContext.Response.ContentType = "application/json"; - await httpContext.Response.WriteAsJsonAsync(new - { - error = "MembershipRequired", - message = "Access denied. You must be an Admin or Operator for this tenant. Use the Progress Portal for session monitoring." - }); - } - - private static bool IsExemptRoute(string path) - { - foreach (var prefix in _exemptPrefixes) - { - if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) - return true; - } - return false; - } -} diff --git a/src/Backend/AutopilotMonitor.Functions/Middleware/PolicyAuditMiddleware.cs b/src/Backend/AutopilotMonitor.Functions/Middleware/PolicyEnforcementMiddleware.cs similarity index 70% rename from src/Backend/AutopilotMonitor.Functions/Middleware/PolicyAuditMiddleware.cs rename to src/Backend/AutopilotMonitor.Functions/Middleware/PolicyEnforcementMiddleware.cs index 5b461d15..ca212ecd 100644 --- a/src/Backend/AutopilotMonitor.Functions/Middleware/PolicyAuditMiddleware.cs +++ b/src/Backend/AutopilotMonitor.Functions/Middleware/PolicyEnforcementMiddleware.cs @@ -12,22 +12,22 @@ using Microsoft.Extensions.Logging; namespace AutopilotMonitor.Functions.Middleware; /// -/// Logging-only middleware that evaluates the EndpointAccessPolicyCatalog against each request -/// and compares its decision with the actual pipeline outcome. Never blocks requests. +/// Policy enforcement middleware that evaluates the EndpointAccessPolicyCatalog against each request +/// and blocks requests that don't meet the required policy. Fail-closed: unregistered routes are denied. /// -/// Designed for Phase 2 of the auth refactor: deploy to production, observe mismatches, -/// then switch to enforcement in Phase 3. +/// Phase 3 of the auth refactor: replaces both PolicyAuditMiddleware (logging-only) and +/// MemberAuthorizationMiddleware (coarse member check) with catalog-driven enforcement. /// -/// Middleware order: AuthenticationMiddleware → PolicyAuditMiddleware → MemberAuthorizationMiddleware +/// Middleware order: AuthenticationMiddleware → PolicyEnforcementMiddleware → Function /// -public class PolicyAuditMiddleware : IFunctionsWorkerMiddleware +public class PolicyEnforcementMiddleware : IFunctionsWorkerMiddleware { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly GalacticAdminService _galacticAdminService; private readonly TenantAdminsService _tenantAdminsService; - public PolicyAuditMiddleware( - ILogger logger, + public PolicyEnforcementMiddleware( + ILogger logger, GalacticAdminService galacticAdminService, TenantAdminsService tenantAdminsService) { @@ -41,6 +41,7 @@ public class PolicyAuditMiddleware : IFunctionsWorkerMiddleware var httpContext = context.GetHttpContext(); if (httpContext == null) { + // Non-HTTP trigger (e.g. timer, queue) — no auth needed await next(context); return; } @@ -53,53 +54,57 @@ public class PolicyAuditMiddleware : IFunctionsWorkerMiddleware if (catalogEntry == null) { - _logger.LogWarning("[PolicyAudit] UNREGISTERED route: {Method} {Path} — not in catalog (fail-closed)", + // Fail-closed: unregistered route → 403 + _logger.LogError("[PolicyEnforcement] BLOCKED unregistered route: {Method} {Path} — not in catalog (fail-closed)", httpMethod, requestPath); + httpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; + httpContext.Response.ContentType = "application/json"; + await httpContext.Response.WriteAsJsonAsync(new + { + error = "Forbidden", + message = "Access denied." + }); + return; + } + + // Evaluate the catalog policy for the current user + var decision = await EvaluateCatalogPolicyAsync(context, catalogEntry); + + if (decision.IsAllowed) + { + _logger.LogDebug("[PolicyEnforcement] ALLOW {Method} {Path} policy={Policy} user={User} role={Role}", + httpMethod, requestPath, catalogEntry.Policy, decision.UserIdentifier, decision.UserRole); await next(context); return; } - // Evaluate what the catalog WOULD decide for the current user - var catalogDecision = await EvaluateCatalogPolicyAsync(context, catalogEntry); + // Denied — determine 401 vs 403 + var statusCode = decision.Reason is "NoJWT" or "MissingClaims" + ? HttpStatusCode.Unauthorized + : HttpStatusCode.Forbidden; - // Let the rest of the pipeline run (MemberAuth + function) - await next(context); + _logger.LogWarning("[PolicyEnforcement] DENIED {Method} {Path} policy={Policy} status={Status} user={User} role={Role} reason={Reason}", + httpMethod, requestPath, catalogEntry.Policy, (int)statusCode, + decision.UserIdentifier, decision.UserRole, decision.Reason); - // Compare catalog decision against actual outcome - var actualStatusCode = httpContext.Response.StatusCode; - var isAuthFailure = actualStatusCode == (int)HttpStatusCode.Unauthorized - || actualStatusCode == (int)HttpStatusCode.Forbidden; - var actuallyAllowed = !isAuthFailure; + httpContext.Response.StatusCode = (int)statusCode; + httpContext.Response.ContentType = "application/json"; - if (catalogDecision.IsAllowed == actuallyAllowed) + if (statusCode == HttpStatusCode.Unauthorized) { - // Decisions match — no mismatch - _logger.LogDebug("[PolicyAudit] OK {Method} {Path} policy={Policy} catalogDecision={Decision} actualStatus={Status}", - httpMethod, requestPath, catalogEntry.Policy, catalogDecision.IsAllowed ? "Allow" : "Deny", actualStatusCode); - return; + await httpContext.Response.WriteAsJsonAsync(new + { + error = "AuthenticationRequired", + message = "Authentication required. Please provide a valid JWT token." + }); } - - // Mismatch detected - if (catalogDecision.IsAllowed && !actuallyAllowed) + else { - // Catalog says Allow but pipeline denied → pipeline is MORE restrictive than catalog - // Expected for Viewer role (MemberAuth blocks, catalog would allow MemberRead) - _logger.LogWarning( - "[PolicyAudit] MISMATCH(TooRestrictive) {Method} {Path} " + - "policy={Policy} catalogDecision=Allow actualStatus={Status} " + - "user={User} role={Role} reason={Reason}", - httpMethod, requestPath, catalogEntry.Policy, actualStatusCode, - catalogDecision.UserIdentifier, catalogDecision.UserRole, catalogDecision.Reason); - } - else if (!catalogDecision.IsAllowed && actuallyAllowed) - { - // Catalog says Deny but pipeline allowed → SECURITY CONCERN: pipeline is too permissive - _logger.LogError( - "[PolicyAudit] MISMATCH(TooPermissive) {Method} {Path} " + - "policy={Policy} catalogDecision=Deny actualStatus={Status} " + - "user={User} role={Role} reason={Reason}", - httpMethod, requestPath, catalogEntry.Policy, actualStatusCode, - catalogDecision.UserIdentifier, catalogDecision.UserRole, catalogDecision.Reason); + await httpContext.Response.WriteAsJsonAsync(new + { + error = "InsufficientPermissions", + message = "Access denied. You do not have permission to access this resource." + }); } } @@ -162,7 +167,7 @@ public class PolicyAuditMiddleware : IFunctionsWorkerMiddleware if (role == null) return CatalogDecisionResult.Deny(userIdentifier, "NonMember", "NotInTenant"); - // MemberRead allows Admin, Operator, AND Viewer (future Phase 3 behavior) + // MemberRead allows Admin, Operator, AND Viewer return CatalogDecisionResult.Allow(userIdentifier, role.Role ?? "Admin", "TenantMember"); } diff --git a/src/Backend/AutopilotMonitor.Functions/Program.cs b/src/Backend/AutopilotMonitor.Functions/Program.cs index 5c68a790..633966ce 100644 --- a/src/Backend/AutopilotMonitor.Functions/Program.cs +++ b/src/Backend/AutopilotMonitor.Functions/Program.cs @@ -19,10 +19,9 @@ var builder = FunctionsApplication.CreateBuilder(args); builder.ConfigureFunctionsWebApplication(); // Register middleware pipeline (Azure Functions .NET 8 isolated worker pattern) -// Order matters: authentication → policy audit (logging-only) → member authorization +// Order matters: authentication (401) → policy enforcement (403) builder.UseMiddleware(); -builder.UseMiddleware(); -builder.UseMiddleware(); +builder.UseMiddleware(); // Configure JSON serialization to use camelCase builder.Services.Configure(options =>