Files
lmxopcua/src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Entities/ApiKey.cs
T
Joseph Doherty 826312befe docs: fill missing XML docs and strip tracking IDs from src comments
Clears CommentChecker findings in src/ (3,280 -> 209, the remainder being false
positives like PLC-5/IEEE-754/M0.0) by adding missing summaries/params/returns,
normalising inheritdoc usage, and removing Gitea/review tracking IDs while keeping
the rationale they annotated. Comment-only: comment-stripped sources are identical
to the parent commit.
2026-10-15 06:14:58 -04:00

68 lines
3.3 KiB
C#

namespace ZB.MOM.WW.OtOpcUa.Configuration.Entities;
/// <summary>
/// One OPC UA data-plane API key. The OPC UA data plane is machine-to-machine, so a
/// consuming system authenticates with a key rather than an LDAP user, and the key resolves to a
/// <see cref="Principal"/> that <see cref="NodeAcl"/> rows grant permissions to.
/// </summary>
/// <remarks>
/// <para>
/// <b>One principal per key, deliberately.</b> The reason to separate keys from principals is so
/// that many keys can share one grant set and rotating a key never touches an ACL row — and that
/// holds with a single principal per key, because nothing stops two keys naming the same
/// principal. A key belonging to several principals at once (union of grants) buys little and
/// costs a join table, so it is not modelled. <c>UserAuthorizationState.Principals</c> is still a
/// list, so adding it later needs no change on the ACL side.
/// </para>
/// <para>
/// <b>The secret is never stored.</b> <see cref="SecretHash"/> holds a peppered HMAC-SHA256 of the
/// secret half only; the plaintext is shown once at creation and is unrecoverable afterwards.
/// </para>
/// <para>
/// ⚠️ <b>Revocation requires a deploy.</b> Phase 4 leaves driver-only nodes with no ConfigDb, so
/// keys reach them inside the deployment artifact exactly as <see cref="NodeAcl"/> rows do.
/// Setting <see cref="Enabled"/> to false takes effect on a node only once a new artifact reaches
/// it. This is an operational sharp edge, not an oversight.
/// </para>
/// </remarks>
public sealed class ApiKey
{
/// <summary>Gets or sets the database row ID for this key.</summary>
public Guid ApiKeyRowId { get; set; }
/// <summary>
/// Gets or sets the public key id — the middle segment of <c>otua_&lt;keyId&gt;_&lt;secret&gt;</c>.
/// Carried in the clear so verification is an index seek rather than a scan over every stored hash.
/// </summary>
public required string ApiKeyId { get; set; }
/// <summary>Gets or sets the human-readable label, e.g. the consuming system's name.</summary>
public required string Name { get; set; }
/// <summary>
/// Gets or sets the base64 peppered HMAC-SHA256 of the key's secret segment. Never the secret.
/// </summary>
public required string SecretHash { get; set; }
/// <summary>
/// Gets or sets the principal this key authenticates as. <see cref="NodeAcl.Principal"/> rows
/// naming this value grant it permissions.
/// </summary>
public required string Principal { get; set; }
/// <summary>Gets or sets whether the key may authenticate. See the revocation caveat on the type.</summary>
public bool Enabled { get; set; } = true;
/// <summary>Gets or sets when the key was created (UTC).</summary>
public DateTime CreatedUtc { get; set; }
/// <summary>Gets or sets when the key expires (UTC); null never expires.</summary>
public DateTime? ExpiresUtc { get; set; }
/// <summary>Gets or sets optional operator notes.</summary>
public string? Notes { get; set; }
/// <summary>Optimistic concurrency token, matching the rest of the live-edit config model.</summary>
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}