using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; using ZB.MOM.WW.OtOpcUa.Configuration.Entities; using ZB.MOM.WW.OtOpcUa.Configuration.Enums; namespace ZB.MOM.WW.OtOpcUa.Host.OpcUa; /// /// reading the config database directly (#520, stage 2). Usable on a node /// that has a ConfigDb — i.e. one carrying the admin role, including a fused admin+driver node. /// /// /// A driver-only node has no ConfigDb since per-cluster mesh Phase 4, so this store cannot be /// registered there; that node uses instead (#520 stage 4). /// /// Reading per request is what makes revocation immediate here, which is why this store is /// preferred wherever a ConfigDb exists — including on a fused admin+driver node. /// /// public sealed class ConfigDbApiKeyStore : IApiKeyStore { private readonly IDbContextFactory _factory; /// Initializes a new instance of the class. /// The config DB context factory. public ConfigDbApiKeyStore(IDbContextFactory factory) => _factory = factory; /// public async Task FindAsync(string apiKeyId, DateTime utcNow, CancellationToken ct) { await using var db = await _factory.CreateDbContextAsync(ct).ConfigureAwait(false); // Enabled + expiry are filtered in SQL rather than in memory so a disabled key never // materialises into a process where something might later treat it as a match. var key = await db.ApiKeys .AsNoTracking() .SingleOrDefaultAsync( k => k.ApiKeyId == apiKeyId && k.Enabled && (k.ExpiresUtc == null || k.ExpiresUtc > utcNow), ct) .ConfigureAwait(false); if (key is null) return null; return new ApiKeyLookup(key, await LoadClusterGrantsAsync(db, key.Principal, ct).ConfigureAwait(false)); } /// public async Task> FindGrantsAsync(string principal, CancellationToken ct) { await using var db = await _factory.CreateDbContextAsync(ct).ConfigureAwait(false); return await LoadClusterGrantsAsync(db, principal, ct).ConfigureAwait(false); } private static async Task> LoadClusterGrantsAsync( OtOpcUaConfigDbContext db, string principal, CancellationToken ct) { // Only cluster-scoped rows can confer an interim role (see ApiKeyInterimRoleMapper), so only // those are fetched. Stage 5 widens this to the full grant set for per-node evaluation. return await db.NodeAcls .AsNoTracking() .Where(a => a.Principal == principal && a.ScopeKind == NodeAclScopeKind.Cluster) .ToListAsync(ct) .ConfigureAwait(false); } }