namespace ZB.MOM.WW.OtOpcUa.Core.Abstractions;
///
/// ⚠️ DEPRECATED compatibility path (lmxopcua#815) — the ONE switch between "enforce" and "warn-only" credential
/// resolution, and it is chosen by CALL SITE, never by a global flag: a deployed, running driver (Galaxy
/// InitializeAsync/ReopenAsync + ack fallback, OpcUaClient session open) passes its instance's tracker
/// and so keeps resolving a pre-#815 out-of-namespace reference with its OLD semantics, warning instead of
/// faulting; every admin-node path (Test Connect probe, AdminUI live browse) and Heidenhain pass
/// and stay hard-fail.
///
///
///
/// Why it is safe to keep for running drivers: the AdminUI save path and the deploy gate refuse every
/// out-of-namespace reference, so a Designer can no longer CREATE one — what remains are configs deployed
/// before the upgrade, whose endpoint and reference an administrator already deployed. Such a config runs
/// only until its next deploy (which the gate refuses until it is migrated).
///
///
/// Removing it (the planned hard-fail release): delete this type, the per-driver Legacy… resolve
/// methods it gates and the legacy parameters; every caller then takes the enforce branch it already
/// has. Nothing else changes.
///
///
/// Each use is reported through at most once per per field
/// (a Galaxy gateway reconnect re-resolves every time), and is exposed as so the
/// driver's bag carries it to the AdminUI (/hosts and the /raw
/// credential banner). Messages name the driver, field and allowed forms — never the value.
///
///
/// The driver's scope (for the allowed forms).
/// The driver instance (for messages).
/// Sink for a rate-limited warning line.
/// Clock (tests); null ⇒ .
public sealed class LegacyCredentialReferenceTracker(
CredentialScope scope, string driverInstanceId, Action warn, Func? clock = null)
{
/// The key prefix (one entry per field).
public const string DiagnosticsKeyPrefix = "credential-compat:";
/// Minimum spacing between two warnings for the same field.
public static readonly TimeSpan WarnInterval = TimeSpan.FromHours(1);
private readonly object _gate = new();
private readonly Dictionary _lastWarned = new(StringComparer.Ordinal);
private readonly SortedDictionary _diagnostics = new(StringComparer.Ordinal);
/// The scope this tracker reports against.
public CredentialScope Scope => scope;
/// The current legacy uses, keyed credential-compat:<field>.
public IReadOnlyDictionary Diagnostics
{
get
{
lock (_gate) return new Dictionary(_diagnostics, StringComparer.Ordinal);
}
}
/// Records that was resolved through the legacy path (warns, rate-limited).
/// The field label.
public void Report(string field)
{
var forms = $"secret:{scope.SecretNamespaces[0]} or env:{scope.EnvVariablePrefixes[0]}";
Record(
field,
$"out-of-namespace ref still resolved (#815 warn-only, deprecated); next deploy refuses it — use {forms}",
$"{scope.Label} driver '{driverInstanceId}': {field} is a credential reference outside the {scope.Label} "
+ $"namespace. It is still resolved for this already-deployed driver (lmxopcua#815 warn-only "
+ $"compatibility — deprecated, a later release fails closed) but the deploy gate refuses it: migrate "
+ $"it to {forms} before the next deploy. See docs/security.md §\"Credential references are namespaced per driver\".");
}
///
/// Records that the key-material file field (lmxopcua#836, e.g. OpcUaClient
/// UserCertificatePath) was loaded from a path outside the node's certificate directory through the same
/// warn-only window (warns, rate-limited). Never carries the path.
///
/// The field label.
/// How the operator configures the directory.
public void ReportPath(string field, string directoryDescription) =>
Record(
field,
$"path outside the certificate directory still loaded (#836 warn-only, deprecated); next deploy refuses it — "
+ $"move the file under {directoryDescription} and author it as a relative path",
$"{scope.Label} driver '{driverInstanceId}': {field} is not a relative path inside the node's certificate "
+ $"directory ({directoryDescription}). It is still loaded for this already-deployed driver (lmxopcua#836 "
+ "warn-only compatibility — deprecated, a later release fails closed) but the deploy gate refuses it: move "
+ "the file into that directory and set the field to its relative path before the next deploy. See "
+ "docs/security.md §\"Key-material file paths are confined to a node directory\".");
private void Record(string field, string diagnostic, string warning)
{
var now = (clock ?? (() => DateTime.UtcNow))();
bool log;
lock (_gate)
{
_diagnostics[DiagnosticsKeyPrefix + field] = diagnostic;
log = !_lastWarned.TryGetValue(field, out var last) || now - last >= WarnInterval;
if (log) _lastWarned[field] = now;
}
if (log) warn(warning);
}
}