Files
scadaproj/ZB.MOM.WW.Auth/src/ZB.MOM.WW.Auth.ApiKeys.SqlServer/SqlServerApiKeyOptions.cs
T

66 lines
3.1 KiB
C#

namespace ZB.MOM.WW.Auth.ApiKeys.SqlServer;
/// <summary>
/// Options for the shared SQL Server API-key store (section supplied by the host, e.g.
/// <c>ScadaBridge:InboundApi:ApiKeyStore:SqlServer</c>).
/// </summary>
public sealed class SqlServerApiKeyOptions
{
/// <summary>
/// Full SQL Server connection string. Required. Never a <c>${secret:}</c> reference on hosts
/// whose secret store is itself SQL-backed.
/// </summary>
public string ConnectionString { get; set; } = string.Empty;
/// <summary>Schema that owns the three tables. Letters, digits and underscore only (it is interpolated into DDL).</summary>
public string Schema { get; set; } = "zbauth";
/// <summary>
/// Command timeout applied to every store operation. Defaults to 30 seconds — long enough for
/// a contended shared database, short enough that a hung server surfaces as an error rather
/// than stalling a resolve indefinitely.
/// </summary>
public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(30);
/// <summary>Fails fast on a blank connection string, an unsafe schema identifier, or a non-positive command timeout.</summary>
/// <exception cref="InvalidOperationException">The options are not usable.</exception>
public void Validate()
{
if (string.IsNullOrWhiteSpace(ConnectionString))
throw new InvalidOperationException("SQL Server API-key store requires a ConnectionString.");
ValidateIdentifier(Schema);
if (CommandTimeout <= TimeSpan.Zero)
throw new InvalidOperationException($"SQL Server API-key store CommandTimeout must be positive (was {CommandTimeout}).");
}
/// <summary>
/// Strict allow-list for a SQL identifier that will be interpolated into DDL or query text.
/// Rejects everything that is not <c>[A-Za-z_][A-Za-z0-9_]{0,63}</c> — no brackets, quotes, dots
/// or spaces — so no bracket-escape (<c>]]</c>) trick can break out of the quoting applied at the
/// use site. Uses the ASCII-only character tests deliberately, so homoglyph and full-width
/// look-alikes are rejected rather than accepted as "letters".
/// </summary>
/// <param name="schemaName">The candidate identifier.</param>
/// <returns>The same value, once validated.</returns>
/// <exception cref="InvalidOperationException">The value is not a plain identifier.</exception>
/// <remarks>
/// Public because <see cref="SqlServerAuthSchema.CreateSchemaDdl(string)"/> is public and must
/// defend itself rather than trust that every caller routed through <see cref="Validate"/>.
/// </remarks>
public static string ValidateIdentifier(string schemaName)
{
if (string.IsNullOrEmpty(schemaName)
|| schemaName.Length > 64
|| (!char.IsAsciiLetter(schemaName[0]) && schemaName[0] != '_')
|| !schemaName.All(c => char.IsAsciiLetterOrDigit(c) || c == '_'))
{
throw new InvalidOperationException(
$"SQL Server API-key store schema name '{schemaName}' is not a safe identifier.");
}
return schemaName;
}
}