feat(auth)!: OtOpcUa canonical control-plane roles + config-DB migration (Task 1.7)
Standardize the control-plane admin role VALUES on the canonical six
(ZB.MOM.WW.Auth CanonicalRole). OtOpcUa uses four:
ConfigViewer -> Viewer
ConfigEditor -> Designer
FleetAdmin -> Administrator
DriverOperator -> Operator (appsettings-only string role)
This is a rename, not a permission change: enforcement semantics are
preserved (whoever could deploy/administer/operate before still can).
- AdminRole enum members renamed (persisted as string names via
HasConversion<string>); RoleGrants.razor dropdown default updated.
- EF DATA migration CanonicalizeAdminRoles rewrites existing
LdapGroupRoleMapping.Role rows old->new (Up) and back (Down); schema /
model snapshot byte-identical (no pending model changes).
- Enforcement role STRINGS canonicalized:
* Security policies keep their NAMES ("DriverOperator"/"FleetAdmin")
but require canonical roles: RequireRole("Operator","Administrator")
and RequireRole("Administrator").
* Deployments.razor [Authorize(Roles="Administrator,Designer")].
* DevStub now grants "Administrator"; LdapOptions/doc-comment examples
canonicalized.
- Data-plane authorization (NodePermissions/NodeAcl/IPermissionEvaluator/
TriePermissionEvaluator/UserAuthorizationState) UNTOUCHED.
- New CanonicalAdminRolesTests pins canonical claim values end-to-end and
the real registered policies; existing role-string tests updated.
This commit is contained in:
+14
-14
@@ -38,7 +38,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task Create_SetsId_AndCreatedAtUtc()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var row = Make("cn=fleet,dc=x", AdminRole.FleetAdmin);
|
||||
var row = Make("cn=fleet,dc=x", AdminRole.Administrator);
|
||||
|
||||
var saved = await svc.CreateAsync(row, CancellationToken.None);
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task Create_Rejects_EmptyLdapGroup()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var row = Make("", AdminRole.FleetAdmin);
|
||||
var row = Make("", AdminRole.Administrator);
|
||||
|
||||
await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
|
||||
() => svc.CreateAsync(row, CancellationToken.None));
|
||||
@@ -62,7 +62,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task Create_Rejects_SystemWide_With_ClusterId()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var row = Make("cn=g", AdminRole.ConfigViewer, clusterId: "c1", isSystemWide: true);
|
||||
var row = Make("cn=g", AdminRole.Viewer, clusterId: "c1", isSystemWide: true);
|
||||
|
||||
await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
|
||||
() => svc.CreateAsync(row, CancellationToken.None));
|
||||
@@ -73,7 +73,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task Create_Rejects_NonSystemWide_WithoutClusterId()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var row = Make("cn=g", AdminRole.ConfigViewer, clusterId: null, isSystemWide: false);
|
||||
var row = Make("cn=g", AdminRole.Viewer, clusterId: null, isSystemWide: false);
|
||||
|
||||
await Should.ThrowAsync<InvalidLdapGroupRoleMappingException>(
|
||||
() => svc.CreateAsync(row, CancellationToken.None));
|
||||
@@ -84,15 +84,15 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task GetByGroups_Returns_MatchingGrants_Only()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=editor,dc=x", AdminRole.ConfigEditor), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=viewer,dc=x", AdminRole.ConfigViewer), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.Administrator), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=editor,dc=x", AdminRole.Designer), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=viewer,dc=x", AdminRole.Viewer), CancellationToken.None);
|
||||
|
||||
var results = await svc.GetByGroupsAsync(
|
||||
["cn=fleet,dc=x", "cn=viewer,dc=x"], CancellationToken.None);
|
||||
|
||||
results.Count.ShouldBe(2);
|
||||
results.Select(r => r.Role).ShouldBe([AdminRole.FleetAdmin, AdminRole.ConfigViewer], ignoreOrder: true);
|
||||
results.Select(r => r.Role).ShouldBe([AdminRole.Administrator, AdminRole.Viewer], ignoreOrder: true);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GetByGroups returns empty when input is empty.</summary>
|
||||
@@ -100,7 +100,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task GetByGroups_Empty_Input_ReturnsEmpty()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.Administrator), CancellationToken.None);
|
||||
|
||||
var results = await svc.GetByGroupsAsync([], CancellationToken.None);
|
||||
|
||||
@@ -112,9 +112,9 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task ListAll_Orders_ByGroupThenCluster()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
await svc.CreateAsync(Make("cn=b,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.ConfigEditor, clusterId: "c2", isSystemWide: false), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.ConfigEditor, clusterId: "c1", isSystemWide: false), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=b,dc=x", AdminRole.Administrator), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.Designer, clusterId: "c2", isSystemWide: false), CancellationToken.None);
|
||||
await svc.CreateAsync(Make("cn=a,dc=x", AdminRole.Designer, clusterId: "c1", isSystemWide: false), CancellationToken.None);
|
||||
|
||||
var results = await svc.ListAllAsync(CancellationToken.None);
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
public async Task Delete_Removes_Matching_Row()
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var saved = await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.FleetAdmin), CancellationToken.None);
|
||||
var saved = await svc.CreateAsync(Make("cn=fleet,dc=x", AdminRole.Administrator), CancellationToken.None);
|
||||
|
||||
await svc.DeleteAsync(saved.Id, CancellationToken.None);
|
||||
|
||||
@@ -153,7 +153,7 @@ public sealed class LdapGroupRoleMappingServiceTests : IDisposable
|
||||
{
|
||||
var svc = new LdapGroupRoleMappingService(_db);
|
||||
var saved = await svc.CreateAsync(
|
||||
Make("cn=sysadmins,dc=x", AdminRole.FleetAdmin, clusterId: null, isSystemWide: true),
|
||||
Make("cn=sysadmins,dc=x", AdminRole.Administrator, clusterId: null, isSystemWide: true),
|
||||
CancellationToken.None);
|
||||
|
||||
saved.IsSystemWide.ShouldBeTrue();
|
||||
|
||||
Reference in New Issue
Block a user