using System.Security.Claims; using Data.SeaHavenIndustries; using SeaHaven.Services.Exceptions; namespace SeaHaven.Services.Helpers { /// /// Claims-derived authorization for work-order media read/mutations. /// Scope is fail-closed: callers need a valid /// or explicit =. /// Absence of scope does not elevate. Staff may access any resulting work order; /// technicians only when matches the actor. /// Delete is staff-only. /// public static class WorkOrderMediaAuthorization { private static readonly string[] StaffRoles = { "Admin", "Manager", "Dispatcher", "Supervisor" }; public static MediaAccountScope ResolveMediaScope(ClaimsPrincipal user) { if (user is null) return new MediaAccountScope.Missing(); var accountRaw = user.FindFirstValue(SeaHavenClaimTypes.AccountId); if (!string.IsNullOrWhiteSpace(accountRaw)) { if (!int.TryParse(accountRaw, out var accountId) || accountId <= 0) return new MediaAccountScope.Missing(); return new MediaAccountScope.Account(accountId); } var orgScope = user.FindFirstValue(SeaHavenClaimTypes.OrgScope); if (string.Equals(orgScope, SeaHavenClaimTypes.OrgScopeAll, StringComparison.Ordinal)) return new MediaAccountScope.OrgWide(); return new MediaAccountScope.Missing(); } public static void EnsureHasMediaScope(ClaimsPrincipal user) { if (ResolveMediaScope(user) is MediaAccountScope.Missing) { throw Forbidden("You are not allowed to access work order media without account scope."); } } public static void EnsureCanRead(ClaimsPrincipal user, string? actorId) { EnsureAuthenticated(user, actorId, "You are not allowed to view work order media."); EnsureHasMediaScope(user); if (IsStaff(user) || user.IsInRole("User")) return; throw Forbidden("You are not allowed to view work order media."); } public static void EnsureCanMutate(ClaimsPrincipal user, string? actorId) { EnsureAuthenticated(user, actorId); EnsureHasMediaScope(user); if (IsStaff(user) || user.IsInRole("User")) return; throw Forbidden(); } public static void EnsureCanDelete(ClaimsPrincipal user, string? actorId) { EnsureAuthenticated(user, actorId); EnsureHasMediaScope(user); // Technician (User) may upload/categorize assigned media but not delete. if (IsStaff(user)) return; throw Forbidden(); } /// /// Caller-scope check after a base (+ account when scoped) work-order load. /// Staff: any resulting work order. /// Technician: only when assigned to the caller. /// Out of caller scope → NotFound (no disclosure). /// public static void EnsureWorkOrderInCallerScope( ClaimsPrincipal user, string actorId, WorkOrder workOrder) { if (IsStaff(user)) return; if (user.IsInRole("User") && string.Equals(workOrder.AssignTo, actorId, StringComparison.Ordinal)) { return; } throw new WorkOrderBoardValidationException("NotFound", "Work order not found."); } private static void EnsureAuthenticated( ClaimsPrincipal user, string? actorId, string? forbiddenMessage = null) { if (user is null || !(user.Identity?.IsAuthenticated ?? false) || string.IsNullOrWhiteSpace(actorId)) { throw Forbidden(forbiddenMessage); } } private static bool IsStaff(ClaimsPrincipal user) => StaffRoles.Any(user.IsInRole); private static WorkOrderBoardValidationException Forbidden(string? message = null) => new( "Forbidden", message ?? "You are not allowed to mutate work order media."); } }