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, post 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: 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";
///
/// Permission to initiate a two-person Secured Write.
/// Satisfied by the role claim. Single-role
/// policy mirroring .
///
public const string RequireOperator = "RequireOperator";
///
/// Permission to approve a two-person Secured Write.
/// Satisfied by the role claim. Kept distinct
/// from so initiation and approval are
/// separable duties.
///
public const string RequireVerifier = "RequireVerifier";
///
/// Read access to the Secured Writes surface (the Secured Writes page +
/// its Pending/history list). The history table exposes process-sensitive
/// tag values, so it is NOT open to any authenticated user — satisfied by
/// any of {, ,
/// } (arch-review UA1). Aligns with the
/// ManagementActor's ListSecuredWritesCommand any-of gate.
///
public const string RequireSecuredWriteAccess = "RequireSecuredWriteAccess";
///
/// Roles that satisfy . Held in one
/// place so the policy, the docs, and the ManagementActor gate stay in
/// lockstep: {Operator, Verifier, Administrator}.
///
public static readonly string[] SecuredWriteAccessRoles =
{ Roles.Operator, Roles.Verifier, Roles.Administrator };
///
/// Read access to the Audit Log surface (Audit Log page,
/// Configuration Audit Log page, Audit nav group). Granted to the
/// Administrator role and the Viewer role (the latter being
/// the 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 — 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.
/// {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. {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));
options.AddPolicy(RequireOperator, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Operator));
options.AddPolicy(RequireVerifier, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, Roles.Verifier));
// Any-of read gate for the Secured Writes surface (arch-review UA1):
// succeeds when the principal holds any of Operator/Verifier/
// Administrator, mirroring the ManagementActor ListSecuredWrites gate.
options.AddPolicy(RequireSecuredWriteAccess, policy =>
policy.RequireClaim(JwtTokenService.RoleClaimType, SecuredWriteAccessRoles));
// 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;
}
}