feat(security): add Operator + Verifier roles + policies + LDAP mapping options (T14a)

This commit is contained in:
Joseph Doherty
2026-06-18 02:07:01 -04:00
parent 5fd77c7155
commit a0ce8b6c44
5 changed files with 90 additions and 4 deletions
@@ -34,6 +34,8 @@
<option value="@Roles.Designer">Designer</option>
<option value="@Roles.Deployer">Deployer</option>
<option value="@Roles.Viewer">Viewer</option>
<option value="@Roles.Operator">Operator</option>
<option value="@Roles.Verifier">Verifier</option>
</select>
<div class="form-text">Deployer role: configure site scope below after saving.</div>
</div>
@@ -74,6 +74,21 @@ public static class AuthorizationPolicies
public const string RequireDesign = "RequireDesign";
public const string RequireDeployment = "RequireDeployment";
/// <summary>
/// Permission to initiate a two-person Secured Write (M7-A3 / T14a).
/// Satisfied by the <see cref="Roles.Operator"/> role claim. Single-role
/// policy mirroring <see cref="RequireAdmin"/>.
/// </summary>
public const string RequireOperator = "RequireOperator";
/// <summary>
/// Permission to approve a two-person Secured Write (M7-A3 / T14a).
/// Satisfied by the <see cref="Roles.Verifier"/> role claim. Kept distinct
/// from <see cref="RequireOperator"/> so initiation and approval are
/// separable duties.
/// </summary>
public const string RequireVerifier = "RequireVerifier";
/// <summary>
/// Read access to the Audit Log #23 surface (Audit Log page,
/// Configuration Audit Log page, Audit nav group). Granted to the
@@ -135,6 +150,12 @@ public static class AuthorizationPolicies
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));
// 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
+19 -3
View File
@@ -28,8 +28,16 @@ namespace ZB.MOM.WW.ScadaBridge.Security;
/// <item><description><c>AuditReadOnly</c> → <c>Viewer</c> (COLLAPSE — keeps
/// audit-read + nav, loses bulk export, which it never had)</description></item>
/// </list>
/// <c>Operator</c> and <c>Engineer</c> exist in the canonical vocabulary but are
/// unused by ScadaBridge, so they are intentionally not declared here.
/// <c>Engineer</c> exists in the canonical vocabulary but is unused by
/// ScadaBridge, so it is intentionally not declared here. <c>Operator</c> is
/// now declared (M7-A3 / T14a) for the two-person Secured Writes feature.
/// </para>
/// <para>
/// Secured Writes (M7-A3 / T14a): <c>Operator</c> initiates a secured write and
/// <c>Verifier</c> approves it — two distinct global roles so a single principal
/// cannot both initiate and approve (separation of duties). Both are coarse
/// global roles, matching the existing role model; site scoping (if any) is
/// layered on at the LDAP-mapping level like the other roles.
/// </para>
/// </remarks>
public static class Roles
@@ -39,7 +47,15 @@ public static class Roles
public const string Deployer = "Deployer";
public const string Viewer = "Viewer";
/// <summary>Initiates a two-person Secured Write (M7-A3 / T14a). Canonical
/// vocabulary role; pairs with <see cref="Verifier"/> who approves.</summary>
public const string Operator = "Operator";
/// <summary>Approves a two-person Secured Write (M7-A3 / T14a). Held by a
/// principal distinct from the initiating <see cref="Operator"/>.</summary>
public const string Verifier = "Verifier";
/// <summary>All declared ScadaBridge roles — the single source of truth for "all
/// permissions" (e.g. the dev auto-login principal). Stays in sync if a role is added.</summary>
public static readonly string[] All = [Administrator, Designer, Deployer, Viewer];
public static readonly string[] All = [Administrator, Designer, Deployer, Viewer, Operator, Verifier];
}