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>
33 lines
1.6 KiB
C#
33 lines
1.6 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Admin.Security;
|
|
|
|
/// <summary>A cluster-scoped Admin-role grant — the <see cref="Role"/> binds only within <see cref="ClusterId"/>.</summary>
|
|
public sealed record ClusterRoleGrant(string ClusterId, string Role);
|
|
|
|
/// <summary>
|
|
/// The Admin roles a user holds after sign-in, split by scope. <see cref="FleetRoles"/> apply
|
|
/// across every cluster; each entry in <see cref="ClusterRoles"/> binds only within its named
|
|
/// cluster. Resolved by <see cref="IAdminRoleGrantResolver"/> from the user's LDAP groups.
|
|
/// </summary>
|
|
public sealed record AdminRoleGrants(
|
|
IReadOnlyList<string> FleetRoles,
|
|
IReadOnlyList<ClusterRoleGrant> ClusterRoles)
|
|
{
|
|
/// <summary>No grants — sign-in is blocked when a resolution yields this.</summary>
|
|
public static readonly AdminRoleGrants Empty = new([], []);
|
|
|
|
/// <summary>True when the user holds no Admin role at any scope.</summary>
|
|
public bool IsEmpty => FleetRoles.Count == 0 && ClusterRoles.Count == 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the Admin-role grants a set of LDAP groups confers. Augments the static
|
|
/// <see cref="LdapOptions.GroupToRole"/> bootstrap dictionary (always fleet-wide) with the
|
|
/// DB-backed <c>LdapGroupRoleMapping</c> rows authored on the role-grants page — fleet-wide
|
|
/// and cluster-scoped. The static dictionary is the lock-out-proof fallback; DB grants stack
|
|
/// additively on top of it.
|
|
/// </summary>
|
|
public interface IAdminRoleGrantResolver
|
|
{
|
|
Task<AdminRoleGrants> ResolveAsync(IReadOnlyList<string> ldapGroups, CancellationToken cancellationToken);
|
|
}
|