using Microsoft.Extensions.Options;
using ZB.MOM.WW.OtOpcUa.Configuration.Services;
namespace ZB.MOM.WW.OtOpcUa.Admin.Security;
///
/// Default . Merges the static appsettings.json
/// bootstrap dictionary with the DB-backed LdapGroupRoleMapping rows. See
/// for the scope split and the decision-#150 control-plane note.
///
public sealed class AdminRoleGrantResolver(
ILdapGroupRoleMappingService mappingService,
IOptions ldapOptions) : IAdminRoleGrantResolver
{
private readonly LdapOptions _ldap = ldapOptions.Value;
public async Task ResolveAsync(
IReadOnlyList ldapGroups, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(ldapGroups);
if (ldapGroups.Count == 0) return AdminRoleGrants.Empty;
// Static bootstrap dictionary — always fleet-wide, lock-out-proof fallback.
var fleet = new HashSet(
RoleMapper.Map(ldapGroups, _ldap.GroupToRole), StringComparer.OrdinalIgnoreCase);
// DB-backed grants stack additively. A system-wide row folds into the fleet set;
// a cluster-scoped row becomes a (cluster, role) grant, deduped on that pair.
var mappings = await mappingService.GetByGroupsAsync(ldapGroups, cancellationToken)
.ConfigureAwait(false);
var cluster = new Dictionary<(string, string), ClusterRoleGrant>();
foreach (var m in mappings)
{
var roleName = m.Role.ToString();
if (m.IsSystemWide || string.IsNullOrEmpty(m.ClusterId))
{
fleet.Add(roleName);
}
else
{
var key = (m.ClusterId, roleName);
cluster[key] = new ClusterRoleGrant(m.ClusterId, roleName);
}
}
return new AdminRoleGrants([.. fleet], [.. cluster.Values]);
}
}