Files
AutopilotMonitor/src/Backend/AutopilotMonitor.Functions/Functions/Progress/ProgressPortalFunction.cs
T
Claude b3187d9ef3 Migrate AuditLogs, RuleResults, Progress, Feedback, Auth functions to DAL
- GetAuditLogsFunction, GetGlobalAuditLogsFunction → IMaintenanceRepository
- GetRuleResultsFunction → IRuleRepository + IMaintenanceRepository + ISessionRepository
- ProgressPortalFunction, FeedbackFunction → ISessionRepository
- AuthFunction → IMetricsRepository

https://claude.ai/code/session_01NHWJfPaa8TQHuPzMD7gG63
2026-03-27 06:42:06 +00:00

170 lines
6.4 KiB
C#

using System.Net;
using System.Web;
using AutopilotMonitor.Functions.Helpers;
using AutopilotMonitor.Functions.Services;
using AutopilotMonitor.Shared.DataAccess;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
namespace AutopilotMonitor.Functions.Functions.Progress;
/// <summary>
/// Dedicated Progress Portal endpoints.
/// These routes are exempt from MemberAuthorizationMiddleware so any authenticated tenant user
/// can monitor enrollment sessions without needing an Admin or Operator role.
/// </summary>
public class ProgressPortalFunction
{
private readonly ILogger<ProgressPortalFunction> _logger;
private readonly ISessionRepository _sessionRepo;
private readonly GlobalAdminService _globalAdminService;
public ProgressPortalFunction(
ILogger<ProgressPortalFunction> logger,
ISessionRepository sessionRepo,
GlobalAdminService globalAdminService)
{
_logger = logger;
_sessionRepo = sessionRepo;
_globalAdminService = globalAdminService;
}
/// <summary>
/// GET /api/progress/sessions
/// Returns sessions for the authenticated user's tenant.
/// </summary>
[Function("ProgressGetSessions")]
public async Task<HttpResponseData> GetSessions(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "progress/sessions")] HttpRequestData req)
{
_logger.LogInformation("ProgressGetSessions processing request");
try
{
// Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware
var tenantId = TenantHelper.GetTenantId(req);
_logger.LogInformation("ProgressGetSessions: Fetching sessions for tenant {TenantId}", tenantId);
var page = await _sessionRepo.GetSessionsAsync(tenantId, maxResults: 100);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new
{
success = true,
count = page.Sessions.Count,
sessions = page.Sessions
});
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in ProgressGetSessions");
var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
await errorResponse.WriteAsJsonAsync(new
{
success = false,
message = "Internal server error",
count = 0,
sessions = Array.Empty<object>()
});
return errorResponse;
}
}
/// <summary>
/// GET /api/progress/sessions/{sessionId}/events
/// Returns events for a specific session. Cross-tenant access only for Global Admins.
/// </summary>
[Function("ProgressGetSessionEvents")]
public async Task<HttpResponseData> GetSessionEvents(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "progress/sessions/{sessionId}/events")] HttpRequestData req,
string sessionId)
{
if (string.IsNullOrEmpty(sessionId))
{
var badRequestResponse = req.CreateResponse(HttpStatusCode.BadRequest);
await badRequestResponse.WriteAsJsonAsync(new { success = false, message = "SessionId is required" });
return badRequestResponse;
}
var sessionPrefix = $"[Session: {sessionId.Substring(0, Math.Min(8, sessionId.Length))}]";
_logger.LogInformation("{SessionPrefix} ProgressGetSessionEvents: Fetching events", sessionPrefix);
try
{
// Authentication + AuthenticatedUser authorization enforced by PolicyEnforcementMiddleware
var userTenantId = TenantHelper.GetTenantId(req);
var userIdentifier = TenantHelper.GetUserIdentifier(req);
var query = HttpUtility.ParseQueryString(req.Url.Query);
var requestedTenantId = query["tenantId"];
if (string.IsNullOrEmpty(requestedTenantId))
{
var badRequest = req.CreateResponse(HttpStatusCode.BadRequest);
await badRequest.WriteAsJsonAsync(new
{
success = false,
message = "tenantId query parameter is required",
sessionId = sessionId,
count = 0,
events = Array.Empty<object>()
});
return badRequest;
}
// Cross-tenant access only for Global Admins
if (requestedTenantId != userTenantId)
{
var isGlobalAdmin = await _globalAdminService.IsGlobalAdminAsync(userIdentifier);
if (!isGlobalAdmin)
{
var forbiddenResponse = req.CreateResponse(HttpStatusCode.Forbidden);
await forbiddenResponse.WriteAsJsonAsync(new
{
success = false,
message = "Access denied. You can only view events for sessions in your own tenant.",
sessionId = sessionId,
count = 0,
events = Array.Empty<object>()
});
return forbiddenResponse;
}
_logger.LogInformation("{SessionPrefix} Global Admin {User} accessing cross-tenant progress events (tenant: {TenantId})",
sessionPrefix, userIdentifier, requestedTenantId);
}
var events = await _sessionRepo.GetSessionEventsAsync(requestedTenantId, sessionId);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new
{
success = true,
sessionId = sessionId,
count = events.Count,
events = events
});
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in ProgressGetSessionEvents for session {SessionId}", sessionId);
var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
await errorResponse.WriteAsJsonAsync(new
{
success = false,
message = "Internal server error",
sessionId = sessionId,
count = 0,
events = Array.Empty<object>()
});
return errorResponse;
}
}
}