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; } }