using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
namespace ZB.MOM.WW.ScadaBridge.Security;
///
/// Centralised authorization policy names + the role→permission mapping
/// that defines them.
///
///
/// The codebase uses a thin role-claim model: each policy expresses a
/// permission, satisfied when the principal carries any role claim
/// () that maps to that
/// permission. Role names are free strings configured via
/// rows
/// (see ) — there is no permission claim, just a
/// fan-out from role to allowed policies.
///
///
///
/// Default role → permission mapping (#23 M7-T15 / Bundle G), post Task 1.7
/// canonicalization + SoD collapse:
///
///
/// Role
/// Policies granted
///
/// -
/// Administrator
/// ,
/// , — admins hold
/// every permission by convention so an Administrator-only user never loses
/// access to a new surface.
///
/// -
/// Designer
///
///
/// -
/// Deployer
///
///
/// -
/// Viewer
/// only — read access to the
/// Audit Log + nav, but NOT . This preserves the
/// half-SoD that the legacy AuditReadOnly role provided (read-not-
/// export) after AuditReadOnly was collapsed into
/// Viewer.
///
///
///
/// SoD collapse (Task 1.7): the legacy distinct audit roles were removed. The
/// former Audit role (full audit surface = read + bulk export) was
/// collapsed into Administrator — a deliberate, accepted privilege
/// escalation (former audit-only users gain the entire admin surface: create
/// sites, manage LDAP mappings/API keys, import bundles). The former
/// AuditReadOnly role (read-only audit) was collapsed into
/// Viewer, which keeps audit-read but correctly LACKS export. The net
/// effect on the audit policies: is granted to
/// {Administrator, Viewer} and only to
/// {Administrator}.
///
/// LDAP group → role mapping is configured via the central UI Admin → LDAP
/// Mappings page (rows in LdapGroupMappings); the same code path
/// reads them whether the role is one of the built-ins above or any
/// future addition. Adding a role here means adding the LDAP mapping row in
/// the deployment; no schema migration is needed.
///
///
public static class AuthorizationPolicies
{
public const string RequireAdmin = "RequireAdmin";
public const string RequireDesign = "RequireDesign";
public const string RequireDeployment = "RequireDeployment";
///
/// Read access to the Audit Log #23 surface (Audit Log page,
/// Configuration Audit Log page, Audit nav group). Granted to the
/// Administrator role and the Viewer role (the latter being
/// the post-Task-1.7 home of the former AuditReadOnly role).
///
public const string OperationalAudit = "OperationalAudit";
///
/// Permission to pull a bulk CSV export of the Audit Log. Separate from
/// so a Viewer can read the
/// table without being able to exfiltrate it in bulk. Granted to the
/// Administrator role only.
///
public const string AuditExport = "AuditExport";
///
/// Roles that satisfy . Held in one place
/// so the seed/docs and the policy stay in lockstep.
///
///
/// Public so the ManagementService HTTP API (#23 M8) — which gates the
/// /api/audit/* routes with a manual Basic-Auth + LDAP role check
/// rather than the ASP.NET authorization-policy pipeline — can reuse the
/// exact same role set the policy enforces.
/// Task 1.7: {Administrator, Viewer} (was {Admin, Audit,
/// AuditReadOnly} — the audit roles collapsed into Administrator/Viewer).
///
public static readonly string[] OperationalAuditRoles = { Roles.Administrator, Roles.Viewer };
///
/// Roles that satisfy . A strict subset of
/// — read access does NOT imply
/// export permission, so Viewer can read but not export.
///
///
/// Public for the same reason as —
/// the ManagementService /api/audit/export route checks roles
/// against this set directly. Task 1.7: {Administrator} (was
/// {Admin, Audit}).
///
public static readonly string[] AuditExportRoles = { Roles.Administrator };
///
/// Registers the ScadaBridge authorization policies (Admin, Design, Deployment, OperationalAudit, AuditExport).
///
/// The service collection to register into.
/// The same instance, for call chaining.
public static IServiceCollection AddScadaBridgeAuthorization(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(RequireAdmin, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Administrator));
options.AddPolicy(RequireDesign, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Designer));
options.AddPolicy(RequireDeployment, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Deployer));
// Multi-role permission policies — the policy succeeds when the
// principal holds ANY of the mapped roles. RequireClaim with
// multiple allowed values is the right primitive: it checks
// whether *any* role claim's value is in the allowed set, so a
// user with role=Administrator (and nothing else) satisfies the
// OperationalAudit policy, and a user with role=Viewer satisfies
// OperationalAudit but not AuditExport.
options.AddPolicy(OperationalAudit, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, OperationalAuditRoles));
options.AddPolicy(AuditExport, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, AuditExportRoles));
});
return services;
}
}