diff --git a/glauth.md b/glauth.md index 4c3659f..0b57f55 100644 --- a/glauth.md +++ b/glauth.md @@ -67,6 +67,12 @@ GLAuth config — it must be provisioned before dashboard authn or the LDAP live tests work. See [Provisioning the GwAdmin group](#provisioning-the-gwadmin-group) below. +> **Dashboard role value (Task 1.7):** the LDAP `GwAdmin` group now maps to +> the canonical dashboard role **`Administrator`** (was `Admin`); `GwReader` +> maps to `Viewer`. This is a pure value rename via +> `MxGateway:Dashboard:GroupToRole` — same operations are authorized. (This +> dashboard role is distinct from the lowercase gRPC `admin` *API-key scope*.) + ## Two bind patterns ### 1. Direct bind (simplest) diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs index fa0b6b7..3caf61a 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs @@ -8,8 +8,10 @@ public static class DashboardRoles { /// /// Read-write access: API-key CRUD, settings, any state-changing UI. + /// Canonical role value (Task 1.7); formerly "Admin" — pure value + /// rename, the operations this role authorizes are unchanged. /// - public const string Admin = "Admin"; + public const string Admin = "Administrator"; /// /// Read-only access: all pages render but write affordances are hidden. diff --git a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json index f14bc39..f18ae0d 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/appsettings.json +++ b/src/ZB.MOM.WW.MxGateway.Server/appsettings.json @@ -60,7 +60,7 @@ "RecentSessionLimit": 200, "ShowTagValues": false, "GroupToRole": { - "GwAdmin": "Admin", + "GwAdmin": "Administrator", "GwReader": "Viewer" } }, diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs index 2a5aac4..fcb8174 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs @@ -87,7 +87,7 @@ public sealed class GatewayOptionsTests [InlineData("MxGateway:Events:QueueCapacity", "0", "MxGateway:Events:QueueCapacity must be greater than zero.")] [InlineData("MxGateway:Protocol:MaxGrpcMessageBytes", "0", "MxGateway:Protocol:MaxGrpcMessageBytes must be between")] [InlineData("MxGateway:Authentication:PepperSecretName", "", "MxGateway:Authentication:PepperSecretName is required")] - [InlineData("MxGateway:Dashboard:GroupToRole:GwAdmin", "Sysadmin", "MxGateway:Dashboard:GroupToRole['GwAdmin'] must be 'Admin' or 'Viewer'.")] + [InlineData("MxGateway:Dashboard:GroupToRole:GwAdmin", "Sysadmin", "MxGateway:Dashboard:GroupToRole['GwAdmin'] must be 'Administrator' or 'Viewer'.")] public void Validation_InvalidConfiguration_FailsClearly(string key, string value, string expectedFailure) { OptionsValidationException exception = Assert.Throws(() => diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs index 7d02fed..935f7c7 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardGroupRoleMapperTests.cs @@ -85,6 +85,41 @@ public sealed class DashboardGroupRoleMapperTests Assert.Empty(result.Roles); } + /// + /// Task 1.7 (canonical roles): an LDAP user in the admin group must resolve + /// to the canonical role value "Administrator" (not the legacy + /// "Admin"), and the reader group to "Viewer". Asserted with + /// string LITERALS — independent of — so a + /// regression on the constant's value is caught here. + /// + [Fact] + public async Task MapAsync_AdminGroup_ResolvesToCanonicalAdministratorValue() + { + DashboardGroupRoleMapper mapper = CreateMapper(StandardMapping()); + + GroupRoleMapping adminResult = await mapper.MapAsync(["GwAdmin"], CancellationToken.None); + GroupRoleMapping readerResult = await mapper.MapAsync(["GwReader"], CancellationToken.None); + + Assert.Equal("Administrator", Assert.Single(adminResult.Roles)); + Assert.Equal("Viewer", Assert.Single(readerResult.Roles)); + } + + /// + /// Task 1.7: the canonical admin value ("Administrator") passes the + /// admin-only gate while a "Viewer" fails it — asserted with literals, + /// proving enforcement is bound to the new value and the legacy "Admin" + /// string is no longer what authorizes admin actions. + /// + [Fact] + public void AdminOnly_AuthorizesCanonicalAdministratorButNotViewer() + { + IReadOnlyList adminGate = DashboardAuthorizationRequirement.AdminOnly.RequiredRoles; + + Assert.Contains("Administrator", adminGate); + Assert.DoesNotContain("Admin", adminGate); + Assert.DoesNotContain("Viewer", adminGate); + } + /// /// Verifies the extracted shared helper is the single source of truth: it /// produces the same roles the mapper does for the same inputs. diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs index 22aeed2..887bffe 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/GatewayApplicationTests.cs @@ -197,7 +197,7 @@ public sealed class GatewayApplicationTests [InlineData( "MxGateway:Dashboard:GroupToRole:GwAdmin", "BogusRole", - "MxGateway:Dashboard:GroupToRole['GwAdmin'] must be 'Admin' or 'Viewer'.")] + "MxGateway:Dashboard:GroupToRole['GwAdmin'] must be 'Administrator' or 'Viewer'.")] [InlineData( "MxGateway:Ldap:AllowInsecure", "false",