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; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace AutopilotMonitor.Functions.Functions { public class DeviceBlockFunction { private readonly ILogger _logger; private readonly BlockedDeviceService _blockedDeviceService; private readonly GalacticAdminService _galacticAdminService; public DeviceBlockFunction( ILogger logger, BlockedDeviceService blockedDeviceService, GalacticAdminService galacticAdminService) { _logger = logger; _blockedDeviceService = blockedDeviceService; _galacticAdminService = galacticAdminService; } /// GET /api/devices/blocked?tenantId={tenantId} — list all active blocks for a tenant [Function("GetBlockedDevices")] public async Task GetBlockedDevices( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "devices/blocked")] HttpRequestData req) { try { if (!TenantHelper.IsAuthenticated(req)) return await UnauthorizedAsync(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) return await ForbiddenAsync(req); var tenantId = req.Query["tenantId"]; if (string.IsNullOrEmpty(tenantId)) return await BadRequestAsync(req, "tenantId query parameter is required"); var blocked = await _blockedDeviceService.GetBlockedDevicesAsync(tenantId); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { success = true, blocked }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error getting blocked devices"); var response = req.CreateResponse(HttpStatusCode.InternalServerError); await response.WriteAsJsonAsync(new { error = "Internal server error" }); return response; } } /// POST /api/devices/block — block a device temporarily [Function("BlockDevice")] public async Task BlockDevice( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "devices/block")] HttpRequestData req) { try { if (!TenantHelper.IsAuthenticated(req)) return await UnauthorizedAsync(req); 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)) body = await reader.ReadToEndAsync(); JObject json; try { json = JObject.Parse(body); } catch { return await BadRequestAsync(req, "Invalid JSON body"); } var tenantId = json["tenantId"]?.ToString(); var serialNumber = json["serialNumber"]?.ToString(); var durationHours = json["durationHours"]?.Value() ?? 12; var reason = json["reason"]?.ToString(); var action = json["action"]?.ToString() ?? "Block"; if (string.IsNullOrEmpty(tenantId) || string.IsNullOrEmpty(serialNumber)) return await BadRequestAsync(req, "tenantId and serialNumber are required"); if (durationHours < 1 || durationHours > 720) // max 30 days return await BadRequestAsync(req, "durationHours must be between 1 and 720"); if (!string.Equals(action, "Block", StringComparison.OrdinalIgnoreCase) && !string.Equals(action, "Kill", StringComparison.OrdinalIgnoreCase)) return await BadRequestAsync(req, "action must be 'Block' or 'Kill'"); // Normalize casing action = string.Equals(action, "Kill", StringComparison.OrdinalIgnoreCase) ? "Kill" : "Block"; await _blockedDeviceService.BlockDeviceAsync(tenantId, serialNumber, durationHours, userIdentifier, reason, action); var isKill = action == "Kill"; _logger.LogWarning( "Galactic Admin {User} {Action} device {SerialNumber} in tenant {TenantId} for {Hours}h. Reason: {Reason}", userIdentifier, isKill ? "issued KILL signal to" : "blocked", serialNumber, tenantId, durationHours, reason); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { success = true, message = isKill ? $"Device {serialNumber} issued remote kill signal for {durationHours} hours." : $"Device {serialNumber} blocked for {durationHours} hours.", unblockAt = DateTime.UtcNow.AddHours(durationHours), action }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error blocking device"); var response = req.CreateResponse(HttpStatusCode.InternalServerError); await response.WriteAsJsonAsync(new { error = "Internal server error" }); return response; } } /// DELETE /api/devices/block/{encodedSerialNumber}?tenantId={tenantId} — unblock a device [Function("UnblockDevice")] public async Task UnblockDevice( [HttpTrigger(AuthorizationLevel.Anonymous, "delete", Route = "devices/block/{encodedSerialNumber}")] HttpRequestData req, string encodedSerialNumber) { try { if (!TenantHelper.IsAuthenticated(req)) return await UnauthorizedAsync(req); var userIdentifier = TenantHelper.GetUserIdentifier(req); if (!await _galacticAdminService.IsGalacticAdminAsync(userIdentifier)) return await ForbiddenAsync(req); var tenantId = req.Query["tenantId"]; if (string.IsNullOrEmpty(tenantId)) return await BadRequestAsync(req, "tenantId query parameter is required"); var serialNumber = Uri.UnescapeDataString(encodedSerialNumber ?? string.Empty); if (string.IsNullOrEmpty(serialNumber)) return await BadRequestAsync(req, "serialNumber is required"); await _blockedDeviceService.UnblockDeviceAsync(tenantId, serialNumber); _logger.LogInformation( "Galactic Admin {User} unblocked device {SerialNumber} in tenant {TenantId}", userIdentifier, serialNumber, tenantId); var response = req.CreateResponse(HttpStatusCode.OK); await response.WriteAsJsonAsync(new { success = true, message = $"Device {serialNumber} unblocked." }); return response; } catch (Exception ex) { _logger.LogError(ex, "Error unblocking device"); var response = req.CreateResponse(HttpStatusCode.InternalServerError); await response.WriteAsJsonAsync(new { error = "Internal server error" }); return response; } } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- private static async Task UnauthorizedAsync(HttpRequestData req) { var r = req.CreateResponse(HttpStatusCode.Unauthorized); await r.WriteAsJsonAsync(new { success = false, message = "Authentication required." }); return r; } private static async Task ForbiddenAsync(HttpRequestData req) { var r = req.CreateResponse(HttpStatusCode.Forbidden); await r.WriteAsJsonAsync(new { success = false, message = "Access denied. Only Galactic Admins can manage device blocks." }); return r; } private static async Task BadRequestAsync(HttpRequestData req, string message) { var r = req.CreateResponse(HttpStatusCode.BadRequest); await r.WriteAsJsonAsync(new { success = false, message }); return r; } } }