Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ClusterRoleClaimsTests.cs
Joseph Doherty 8adb83afee feat(admin): consume LDAP role grants at sign-in, incl. cluster scoping
The role-grants page authored LdapGroupRoleMapping rows but nothing
consumed them — sign-in only read the static appsettings GroupToRole
dictionary. Wire the DB-backed grants into the auth path.

- AdminRoleGrantResolver merges the static bootstrap dictionary (always
  fleet-wide, lock-out-proof) with DB grants; system-wide rows fold into
  fleet roles, cluster-scoped rows become (cluster, role) grants.
- Login emits a ClaimTypes.Role claim per fleet role and a cluster_role
  claim per cluster-scoped grant; lock-out check spans both scopes.
- ClusterRoleClaims + ClaimsPrincipal extensions resolve the effective
  role for a cluster (highest of fleet-wide and cluster-scoped).
- ClusterAuthorizeView gates cluster pages: ClusterDetail (view +
  ConfigEditor draft actions), DraftEditor (ConfigEditor / FleetAdmin
  publish), DiffViewer (ConfigViewer), ImportEquipment (ConfigEditor).
- RoleGrants page is now FleetAdmin-only; Account surfaces fleet-wide
  and cluster-scoped grants separately.

Control-plane only — decision #150 holds, NodeAcl is untouched.

Tests: AdminRoleGrantResolverTests + ClusterRoleClaimsTests (22).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 03:09:06 -04:00

95 lines
3.2 KiB
C#

using System.Security.Claims;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Admin.Security;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
[Trait("Category", "Unit")]
public sealed class ClusterRoleClaimsTests
{
private static ClaimsPrincipal User(params Claim[] claims)
=> new(new ClaimsIdentity(claims, authenticationType: "test"));
private static Claim Fleet(string role) => new(ClaimTypes.Role, role);
private static Claim Cluster(string clusterId, AdminRole role)
=> new(ClusterRoleClaims.ClaimType, ClusterRoleClaims.Encode(clusterId, role.ToString()));
[Fact]
public void Encode_then_decode_roundtrips()
{
var decoded = ClusterRoleClaims.Decode(ClusterRoleClaims.Encode("WARSAW", "FleetAdmin"));
decoded.ShouldNotBeNull();
decoded!.Value.ClusterId.ShouldBe("WARSAW");
decoded.Value.Role.ShouldBe("FleetAdmin");
}
[Theory]
[InlineData("")]
[InlineData("nosseparator")]
public void Decode_malformed_value_returns_null(string value)
=> ClusterRoleClaims.Decode(value).ShouldBeNull();
[Fact]
public void Effective_role_for_cluster_uses_fleet_wide_grant()
{
var user = User(Fleet("ConfigEditor"));
user.EffectiveClusterRole("WARSAW").ShouldBe(AdminRole.ConfigEditor);
}
[Fact]
public void Effective_role_uses_cluster_scoped_grant_for_the_named_cluster()
{
var user = User(Cluster("WARSAW", AdminRole.FleetAdmin));
user.EffectiveClusterRole("WARSAW").ShouldBe(AdminRole.FleetAdmin);
}
[Fact]
public void Cluster_scoped_grant_does_not_leak_to_another_cluster()
{
var user = User(Cluster("WARSAW", AdminRole.FleetAdmin));
user.EffectiveClusterRole("BERLIN").ShouldBeNull();
}
[Fact]
public void Cluster_match_is_case_insensitive()
{
var user = User(Cluster("WARSAW", AdminRole.ConfigViewer));
user.EffectiveClusterRole("warsaw").ShouldBe(AdminRole.ConfigViewer);
}
[Fact]
public void Effective_role_is_the_highest_of_fleet_and_cluster_grants()
{
var user = User(Fleet("ConfigViewer"), Cluster("WARSAW", AdminRole.FleetAdmin));
user.EffectiveClusterRole("WARSAW").ShouldBe(AdminRole.FleetAdmin);
}
[Fact]
public void Fleet_grant_wins_when_higher_than_the_cluster_grant()
{
var user = User(Fleet("FleetAdmin"), Cluster("WARSAW", AdminRole.ConfigViewer));
user.EffectiveClusterRole("WARSAW").ShouldBe(AdminRole.FleetAdmin);
}
[Fact]
public void No_grants_yields_null_effective_role()
=> User().EffectiveClusterRole("WARSAW").ShouldBeNull();
[Theory]
[InlineData(AdminRole.ConfigViewer, true)]
[InlineData(AdminRole.ConfigEditor, true)]
[InlineData(AdminRole.FleetAdmin, false)]
public void Has_cluster_role_respects_the_minimum(AdminRole minRole, bool expected)
{
var user = User(Cluster("WARSAW", AdminRole.ConfigEditor));
user.HasClusterRole("WARSAW", minRole).ShouldBe(expected);
}
[Fact]
public void Has_cluster_role_is_false_without_any_grant()
=> User().HasClusterRole("WARSAW", AdminRole.ConfigViewer).ShouldBeFalse();
}