using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Core.Authorization;
namespace ZB.MOM.WW.OtOpcUa.Admin.Services;
///
/// Runs an ad-hoc permission probe against a draft or published generation's NodeAcl rows —
/// "if LDAP group X asks for permission Y on node Z, would the trie grant it, and which
/// rows contributed?" Powers the AclsTab "Probe this permission" form per the #196 sub-slice.
///
///
/// Thin wrapper over + —
/// the same code path the Server's dispatch layer uses at request time, so a probe result
/// is guaranteed to match what the live server would decide. The probe is read-only + has
/// no side effects; failing probes do NOT generate audit log rows.
///
public sealed class PermissionProbeService(OtOpcUaConfigDbContext db)
{
///
/// Evaluate against the NodeAcl rows of
/// for a request by at
/// . Returns whether the permission would be granted + the list
/// of matching grants so the UI can show *why*.
///
public async Task ProbeAsync(
long generationId,
string ldapGroup,
NodeScope scope,
NodePermissions required,
CancellationToken ct)
{
ArgumentException.ThrowIfNullOrWhiteSpace(ldapGroup);
ArgumentNullException.ThrowIfNull(scope);
var rows = await db.NodeAcls.AsNoTracking()
.Where(a => a.GenerationId == generationId && a.ClusterId == scope.ClusterId)
.ToListAsync(ct).ConfigureAwait(false);
var trie = PermissionTrieBuilder.Build(scope.ClusterId, generationId, rows);
var matches = trie.CollectMatches(scope, [ldapGroup]);
var effective = NodePermissions.None;
foreach (var m in matches)
effective |= m.PermissionFlags;
var granted = (effective & required) == required;
return new PermissionProbeResult(
Granted: granted,
Required: required,
Effective: effective,
Matches: matches);
}
}
/// Outcome of a call.
public sealed record PermissionProbeResult(
bool Granted,
NodePermissions Required,
NodePermissions Effective,
IReadOnlyList Matches);