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",