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);