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