mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 22:23:12 +00:00
132 lines
4.5 KiB
C#
132 lines
4.5 KiB
C#
using System.Security.Claims;
|
|
using Data.SeaHavenIndustries;
|
|
using SeaHaven.Services.Exceptions;
|
|
|
|
namespace SeaHaven.Services.Helpers
|
|
{
|
|
/// <summary>
|
|
/// Claims-derived authorization for work-order media read/mutations.
|
|
/// Scope is fail-closed: callers need a valid <see cref="SeaHavenClaimTypes.AccountId"/>
|
|
/// or explicit <see cref="SeaHavenClaimTypes.OrgScope"/>=<see cref="SeaHavenClaimTypes.OrgScopeAll"/>.
|
|
/// Absence of scope does not elevate. Staff may access any resulting work order;
|
|
/// technicians only when <see cref="WorkOrder.AssignTo"/> matches the actor.
|
|
/// Delete is staff-only.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
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.");
|
|
}
|
|
}
|