impelented PolicyEnforcementMiddleware

This commit is contained in:
Oliver Kieselbach
2026-03-08 17:29:32 +01:00
parent c4daaa75d1
commit b67f1a447e
52 changed files with 198 additions and 1367 deletions
@@ -1 +1 @@
325
331
@@ -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();
@@ -97,13 +97,7 @@ public class AutopilotDeviceValidationConsentFunction
private async Task<HttpResponseData?> 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);
@@ -15,16 +15,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin
{
private readonly ILogger<DeviceBlockFunction> _logger;
private readonly BlockedDeviceService _blockedDeviceService;
private readonly GalacticAdminService _galacticAdminService;
public DeviceBlockFunction(
ILogger<DeviceBlockFunction> logger,
BlockedDeviceService blockedDeviceService,
GalacticAdminService galacticAdminService)
BlockedDeviceService blockedDeviceService)
{
_logger = logger;
_blockedDeviceService = blockedDeviceService;
_galacticAdminService = galacticAdminService;
}
/// <summary>GET /api/devices/blocked?tenantId={tenantId} — list all active blocks for a tenant</summary>
@@ -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<HttpResponseData> UnauthorizedAsync(HttpRequestData req)
{
var r = req.CreateResponse(HttpStatusCode.Unauthorized);
await r.WriteAsJsonAsync(new { success = false, message = "Authentication required." });
return r;
}
private static async Task<HttpResponseData> 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<HttpResponseData> BadRequestAsync(HttpRequestData req, string message)
{
var r = req.CreateResponse(HttpStatusCode.BadRequest);
@@ -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<GetAllBlockedDevicesFunction> _logger;
private readonly BlockedDeviceService _blockedDeviceService;
private readonly GalacticAdminService _galacticAdminService;
public GetAllBlockedDevicesFunction(
ILogger<GetAllBlockedDevicesFunction> logger,
BlockedDeviceService blockedDeviceService,
GalacticAdminService galacticAdminService)
BlockedDeviceService blockedDeviceService)
{
_logger = logger;
_blockedDeviceService = blockedDeviceService;
_galacticAdminService = galacticAdminService;
}
/// <summary>GET /api/galactic/devices/blocked — list all active blocks across all tenants</summary>
@@ -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();
@@ -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);
@@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin
{
private readonly ILogger<GetGalacticAuditLogsFunction> _logger;
private readonly TableStorageService _storageService;
private readonly GalacticAdminService _galacticAdminService;
public GetGalacticAuditLogsFunction(
ILogger<GetGalacticAuditLogsFunction> 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})");
@@ -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<ReseedFromGitHubFunction> 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";
@@ -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();
@@ -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();
@@ -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<TriggerMaintenanceFunction> _logger;
private readonly MaintenanceService _maintenanceService;
private readonly GalacticAdminService _galacticAdminService;
public TriggerMaintenanceFunction(
ILogger<TriggerMaintenanceFunction> logger,
MaintenanceService maintenanceService,
GalacticAdminService galacticAdminService)
MaintenanceService maintenanceService)
{
_logger = logger;
_maintenanceService = maintenanceService;
_galacticAdminService = galacticAdminService;
}
/// <summary>
@@ -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}");
@@ -15,16 +15,13 @@ namespace AutopilotMonitor.Functions.Functions.Admin
{
private readonly ILogger<VersionBlockFunction> _logger;
private readonly BlockedVersionService _blockedVersionService;
private readonly GalacticAdminService _galacticAdminService;
public VersionBlockFunction(
ILogger<VersionBlockFunction> logger,
BlockedVersionService blockedVersionService,
GalacticAdminService galacticAdminService)
BlockedVersionService blockedVersionService)
{
_logger = logger;
_blockedVersionService = blockedVersionService;
_galacticAdminService = galacticAdminService;
}
/// <summary>GET /api/versions/blocked — list all active version block rules</summary>
@@ -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<HttpResponseData> UnauthorizedAsync(HttpRequestData req)
{
var r = req.CreateResponse(HttpStatusCode.Unauthorized);
await r.WriteAsJsonAsync(new { success = false, message = "Authentication required." });
return r;
}
private static async Task<HttpResponseData> 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<HttpResponseData> BadRequestAsync(HttpRequestData req, string message)
{
var r = req.CreateResponse(HttpStatusCode.BadRequest);
@@ -21,20 +21,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap
private readonly ILogger<CreateBootstrapSessionFunction> _logger;
private readonly BootstrapSessionService _bootstrapService;
private readonly GalacticAdminService _galacticAdminService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly TenantConfigurationService _configService;
public CreateBootstrapSessionFunction(
ILogger<CreateBootstrapSessionFunction> 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)
@@ -20,20 +20,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap
private readonly ILogger<ListBootstrapSessionsFunction> _logger;
private readonly BootstrapSessionService _bootstrapService;
private readonly GalacticAdminService _galacticAdminService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly TenantConfigurationService _configService;
public ListBootstrapSessionsFunction(
ILogger<ListBootstrapSessionsFunction> 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)
@@ -18,20 +18,17 @@ namespace AutopilotMonitor.Functions.Functions.Bootstrap
private readonly ILogger<RevokeBootstrapSessionFunction> _logger;
private readonly BootstrapSessionService _bootstrapService;
private readonly GalacticAdminService _galacticAdminService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly TenantConfigurationService _configService;
public RevokeBootstrapSessionFunction(
ILogger<RevokeBootstrapSessionFunction> 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)
@@ -13,16 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
private readonly ILogger<GetAdminConfigurationFunction> _logger;
private readonly AdminConfigurationService _adminConfigService;
private readonly GalacticAdminService _galacticAdminService;
public GetAdminConfigurationFunction(
ILogger<GetAdminConfigurationFunction> 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();
@@ -13,16 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
private readonly ILogger<GetAllTenantConfigurationsFunction> _logger;
private readonly TenantConfigurationService _configService;
private readonly GalacticAdminService _galacticAdminService;
public GetAllTenantConfigurationsFunction(
ILogger<GetAllTenantConfigurationsFunction> 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();
@@ -13,13 +13,16 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
private readonly ILogger<GetTenantConfigurationFunction> _logger;
private readonly TenantConfigurationService _configService;
private readonly GalacticAdminService _galacticAdminService;
public GetTenantConfigurationFunction(
ILogger<GetTenantConfigurationFunction> 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}");
@@ -16,16 +16,13 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
private readonly ILogger<UpdateAdminConfigurationFunction> _logger;
private readonly AdminConfigurationService _adminConfigService;
private readonly GalacticAdminService _galacticAdminService;
public UpdateAdminConfigurationFunction(
ILogger<UpdateAdminConfigurationFunction> 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
@@ -16,18 +16,15 @@ namespace AutopilotMonitor.Functions.Functions.Config
{
private readonly ILogger<UpdateTenantConfigurationFunction> _logger;
private readonly TenantConfigurationService _configService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
public UpdateTenantConfigurationFunction(
ILogger<UpdateTenantConfigurationFunction> 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)
@@ -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);
@@ -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<AddGalacticAdminRequest>();
@@ -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))
@@ -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<HealthCheckFunction> _logger;
private readonly HealthCheckService _healthCheckService;
private readonly GalacticAdminService _galacticAdminService;
public HealthCheckFunction(
ILogger<HealthCheckFunction> logger,
HealthCheckService healthCheckService,
GalacticAdminService galacticAdminService)
HealthCheckService healthCheckService)
{
_logger = logger;
_healthCheckService = healthCheckService;
_galacticAdminService = galacticAdminService;
}
/// <summary>
@@ -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();
@@ -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)
@@ -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)
@@ -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}");
@@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics
{
private readonly ILogger<GetGalacticAppMetricsFunction> _logger;
private readonly TableStorageService _storageService;
private readonly GalacticAdminService _galacticAdminService;
public GetGalacticAppMetricsFunction(
ILogger<GetGalacticAppMetricsFunction> 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})");
@@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Metrics
{
private readonly ILogger<GetGalacticGeographicMetricsFunction> _logger;
private readonly TableStorageService _storageService;
private readonly GalacticAdminService _galacticAdminService;
public GetGalacticGeographicMetricsFunction(
ILogger<GetGalacticGeographicMetricsFunction> 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);
@@ -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<GetGalacticPlatformMetricsFunction> _logger;
private readonly PlatformMetricsService _platformMetricsService;
private readonly GalacticAdminService _galacticAdminService;
public GetGalacticPlatformMetricsFunction(
ILogger<GetGalacticPlatformMetricsFunction> 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();
@@ -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);
@@ -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<PlatformUsageMetricsFunction> _logger;
private readonly UsageMetricsService _usageMetricsService;
private readonly GalacticAdminService _galacticAdminService;
public PlatformUsageMetricsFunction(
ILogger<PlatformUsageMetricsFunction> logger,
UsageMetricsService usageMetricsService,
GalacticAdminService galacticAdminService)
UsageMetricsService usageMetricsService)
{
_logger = logger;
_usageMetricsService = usageMetricsService;
_galacticAdminService = galacticAdminService;
}
/// <summary>
@@ -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();
@@ -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);
@@ -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<object>()
});
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<object>()
});
return unauthorizedResponse;
}
// Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware
var userTenantId = TenantHelper.GetTenantId(req);
var userIdentifier = TenantHelper.GetUserIdentifier(req);
@@ -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<GetSessionReportDownloadUrlFunction> _logger;
private readonly GalacticAdminService _galacticAdminService;
private readonly string _blobConnectionString;
private const string ContainerName = "session-reports";
public GetSessionReportDownloadUrlFunction(
ILogger<GetSessionReportDownloadUrlFunction> 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"];
@@ -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<GetSessionReportsFunction> _logger;
private readonly SessionReportService _sessionReportService;
private readonly GalacticAdminService _galacticAdminService;
public GetSessionReportsFunction(
ILogger<GetSessionReportsFunction> 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();
@@ -13,7 +13,6 @@ namespace AutopilotMonitor.Functions.Functions.Reports
private readonly ILogger<SubmitSessionReportFunction> _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<SubmitSessionReportFunction> 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<SubmitSessionReportRequest>();
@@ -16,16 +16,13 @@ namespace AutopilotMonitor.Functions.Functions.Reports
{
private readonly ILogger<UpdateSessionReportNoteFunction> _logger;
private readonly SessionReportService _sessionReportService;
private readonly GalacticAdminService _galacticAdminService;
public UpdateSessionReportNoteFunction(
ILogger<UpdateSessionReportNoteFunction> 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))
{
@@ -16,32 +16,21 @@ namespace AutopilotMonitor.Functions.Functions.Rules
{
private readonly ILogger<AnalyzeRulesFunction> _logger;
private readonly AnalyzeRuleService _ruleService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
public AnalyzeRulesFunction(
ILogger<AnalyzeRulesFunction> logger,
AnalyzeRuleService ruleService,
TenantAdminsService tenantAdminsService,
GalacticAdminService galacticAdminService)
AnalyzeRuleService ruleService)
{
_logger = logger;
_ruleService = ruleService;
_tenantAdminsService = tenantAdminsService;
_galacticAdminService = galacticAdminService;
}
[Function("GetAnalyzeRules")]
public async Task<HttpResponseData> 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<HttpResponseData> 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);
@@ -16,32 +16,21 @@ namespace AutopilotMonitor.Functions.Functions.Rules
{
private readonly ILogger<GatherRulesFunction> _logger;
private readonly GatherRuleService _ruleService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
public GatherRulesFunction(
ILogger<GatherRulesFunction> logger,
GatherRuleService ruleService,
TenantAdminsService tenantAdminsService,
GalacticAdminService galacticAdminService)
GatherRuleService ruleService)
{
_logger = logger;
_ruleService = ruleService;
_tenantAdminsService = tenantAdminsService;
_galacticAdminService = galacticAdminService;
}
[Function("GetGatherRules")]
public async Task<HttpResponseData> 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<HttpResponseData> 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);
@@ -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);
@@ -17,26 +17,19 @@ namespace AutopilotMonitor.Functions.Functions.Rules
{
private readonly ILogger<ImeLogPatternsFunction> _logger;
private readonly ImeLogPatternService _patternService;
private readonly GalacticAdminService _galacticAdminService;
public ImeLogPatternsFunction(ILogger<ImeLogPatternsFunction> logger, ImeLogPatternService patternService, GalacticAdminService galacticAdminService)
public ImeLogPatternsFunction(ILogger<ImeLogPatternsFunction> logger, ImeLogPatternService patternService)
{
_logger = logger;
_patternService = patternService;
_galacticAdminService = galacticAdminService;
}
[Function("GetImeLogPatterns")]
public async Task<HttpResponseData> 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}");
@@ -17,20 +17,17 @@ public class PreviewWhitelistFunction
{
private readonly ILogger<PreviewWhitelistFunction> _logger;
private readonly PreviewWhitelistService _previewWhitelistService;
private readonly GalacticAdminService _galacticAdminService;
private readonly TenantConfigurationService _tenantConfigurationService;
private readonly TenantAdminsService _tenantAdminsService;
public PreviewWhitelistFunction(
ILogger<PreviewWhitelistFunction> 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);
@@ -11,19 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
{
private readonly ILogger<DeleteSessionFunction> _logger;
private readonly TableStorageService _storageService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
public DeleteSessionFunction(
ILogger<DeleteSessionFunction> 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
@@ -11,16 +11,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
{
private readonly ILogger<GetAllSessionsFunction> _logger;
private readonly TableStorageService _storageService;
private readonly GalacticAdminService _galacticAdminService;
public GetAllSessionsFunction(
ILogger<GetAllSessionsFunction> 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})");
@@ -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<object>()
});
return unauthorizedResponse;
}
// Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware
// Get user's tenant ID and identifier from JWT
var userTenantId = TenantHelper.GetTenantId(req);
@@ -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);
@@ -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<object>()
});
return unauthorizedResponse;
}
// Authentication + MemberRead authorization enforced by PolicyEnforcementMiddleware
var tenantId = TenantHelper.GetTenantId(req);
_logger.LogInformation($"Fetching sessions for tenant {tenantId}");
@@ -13,19 +13,13 @@ namespace AutopilotMonitor.Functions.Functions.Sessions
{
private readonly ILogger<MarkSessionFailedFunction> _logger;
private readonly TableStorageService _storageService;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
public MarkSessionFailedFunction(
ILogger<MarkSessionFailedFunction> 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
@@ -9,27 +9,6 @@ namespace AutopilotMonitor.Functions.Helpers;
/// </summary>
public static class TenantHelper
{
/// <summary>
/// Checks if the request has an authenticated user
/// Azure Functions Isolated Worker compatible
/// </summary>
/// <param name="req">The HTTP request</param>
/// <returns>True if user is authenticated, false otherwise</returns>
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;
}
/// <summary>
/// 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.
@@ -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;
/// <summary>
/// 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.
/// </summary>
public class MemberAuthorizationMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<MemberAuthorizationMiddleware> _logger;
private readonly TenantAdminsService _tenantAdminsService;
private readonly GalacticAdminService _galacticAdminService;
/// <summary>
/// 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.
/// </summary>
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<MemberAuthorizationMiddleware> 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;
}
}
@@ -12,22 +12,22 @@ using Microsoft.Extensions.Logging;
namespace AutopilotMonitor.Functions.Middleware;
/// <summary>
/// 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
/// </summary>
public class PolicyAuditMiddleware : IFunctionsWorkerMiddleware
public class PolicyEnforcementMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<PolicyAuditMiddleware> _logger;
private readonly ILogger<PolicyEnforcementMiddleware> _logger;
private readonly GalacticAdminService _galacticAdminService;
private readonly TenantAdminsService _tenantAdminsService;
public PolicyAuditMiddleware(
ILogger<PolicyAuditMiddleware> logger,
public PolicyEnforcementMiddleware(
ILogger<PolicyEnforcementMiddleware> 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");
}
@@ -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<AuthenticationMiddleware>();
builder.UseMiddleware<PolicyAuditMiddleware>();
builder.UseMiddleware<MemberAuthorizationMiddleware>();
builder.UseMiddleware<PolicyEnforcementMiddleware>();
// Configure JSON serialization to use camelCase
builder.Services.Configure<JsonSerializerOptions>(options =>