c1e34c307e
Claude-Session: https://claude.ai/code/session_013L3XdmVii9i5sY4FChU8pB
66 lines
3.1 KiB
C#
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;
|
|
}
|
|
}
|