diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor index f93b85f..03540ed 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor +++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor @@ -15,7 +15,7 @@
- Signed in as @context.User.Identity?.Name + Signed in as @context.User.Identity?.Name
@string.Join(", ", context.User.Claims.Where(c => c.Type.EndsWith("/role")).Select(c => c.Value)) diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor new file mode 100644 index 0000000..16aef4c --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Account.razor @@ -0,0 +1,129 @@ +@page "/account" +@attribute [Microsoft.AspNetCore.Authorization.Authorize] +@using System.Security.Claims +@using ZB.MOM.WW.OtOpcUa.Admin.Services + +

My account

+ + + + @{ + var username = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "—"; + var displayName = context.User.Identity?.Name ?? "—"; + var roles = context.User.Claims + .Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value).ToList(); + var ldapGroups = context.User.Claims + .Where(c => c.Type == "ldap_group").Select(c => c.Value).ToList(); + } + +
+
+
+
+
Identity
+
+
Username
@username
+
Display name
@displayName
+
+
+
+
+ +
+
+
+
Admin roles
+ @if (roles.Count == 0) + { +

No Admin roles mapped — sign-in would have been blocked, so if you're seeing this, the session claim is likely stale.

+ } + else + { +
+ @foreach (var r in roles) + { + @r + } +
+ LDAP groups: @(ldapGroups.Count == 0 ? "(none surfaced)" : string.Join(", ", ldapGroups)) + } +
+
+
+ +
+
+
+
Capabilities
+

+ Each Admin role grants a fixed capability set per admin-ui.md §Admin Roles. + Pages below reflect what this session can access; the route's [Authorize] guard + is the ground truth — this table mirrors it for readability. +

+ + + + + + + + + + @foreach (var cap in Capabilities) + { + var has = cap.RequiredRoles.Any(r => roles.Contains(r, StringComparer.OrdinalIgnoreCase)); + + + + + + } + +
CapabilityRequired role(s)You have it?
@cap.Name
@cap.Description
@string.Join(" or ", cap.RequiredRoles) + @if (has) + { + Yes + } + else + { + No + } +
+
+
+
+
+ +
+
+ +
+
+
+
+ +@code { + private sealed record Capability(string Name, string Description, string[] RequiredRoles); + + // Kept in sync with Program.cs authorization policies + each page's [Authorize] attribute. + // When a new page or policy is added, extend this list so operators can self-service check + // whether their session has access without trial-and-error navigation. + private static readonly IReadOnlyList Capabilities = + [ + new("View clusters + fleet status", + "Read-only access to the cluster list, fleet dashboard, and generation history.", + [AdminRoles.ConfigViewer, AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), + new("Edit configuration drafts", + "Create and edit draft generations, manage namespace bindings and node ACLs. CanEdit policy.", + [AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), + new("Publish generations", + "Promote a draft to Published — triggers node roll-out. CanPublish policy.", + [AdminRoles.FleetAdmin]), + new("Manage certificate trust", + "Trust rejected client certs + revoke trust. FleetAdmin-only because the trust decision gates OPC UA client access.", + [AdminRoles.FleetAdmin]), + new("Manage external-ID reservations", + "Reserve / release external IDs that map into Galaxy contained names.", + [AdminRoles.ConfigEditor, AdminRoles.FleetAdmin]), + ]; +}