feat(security): add Operator + Verifier roles + policies + LDAP mapping options (T14a)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class RolesAllTests
|
||||
public void All_ContainsEveryDeclaredRole()
|
||||
{
|
||||
Assert.Equal(
|
||||
new[] { Roles.Administrator, Roles.Designer, Roles.Deployer, Roles.Viewer },
|
||||
new[] { Roles.Administrator, Roles.Designer, Roles.Deployer, Roles.Viewer, Roles.Operator, Roles.Verifier },
|
||||
Roles.All);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Security;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Security.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the role-name string literals and the contents of <see cref="Roles.All"/>.
|
||||
/// Added for M7-A3 (T14a): the two-person Secured Writes feature introduces the
|
||||
/// <c>Operator</c> (initiates) and <c>Verifier</c> (approves) global roles.
|
||||
/// </summary>
|
||||
public class RolesTests
|
||||
{
|
||||
[Fact]
|
||||
public void OperatorConst_HasCanonicalValue()
|
||||
{
|
||||
Assert.Equal("Operator", Roles.Operator);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifierConst_HasCanonicalValue()
|
||||
{
|
||||
Assert.Equal("Verifier", Roles.Verifier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void All_StillContainsOriginalFourRoles()
|
||||
{
|
||||
Assert.Contains(Roles.Administrator, Roles.All);
|
||||
Assert.Contains(Roles.Designer, Roles.All);
|
||||
Assert.Contains(Roles.Deployer, Roles.All);
|
||||
Assert.Contains(Roles.Viewer, Roles.All);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void All_ContainsOperatorAndVerifier()
|
||||
{
|
||||
Assert.Contains("Operator", Roles.All);
|
||||
Assert.Contains("Verifier", Roles.All);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizationPolicies_DeclareOperatorAndVerifierPolicyNames()
|
||||
{
|
||||
Assert.Equal("RequireOperator", AuthorizationPolicies.RequireOperator);
|
||||
Assert.Equal("RequireVerifier", AuthorizationPolicies.RequireVerifier);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user