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