fix(inbound): authorize+secure Database helper, async/deadline-bound DB, wait-timeout-bound WaitForAttribute
Resolves InboundAPI-026/027/028/029 (+ newly-surfaced -030). - 026: authorize the scoped Database helper in the design doc; SQL-injection protection is parameter binding (values never concatenated); allow writes via ExecuteAsync; drop the false 'read-only' claim. Named connections only. - 027: async ADO.NET end-to-end (no .GetAwaiter().GetResult()); honour the method deadline token on ExecuteScalarAsync/ExecuteReaderAsync/ExecuteNonQueryAsync + a CommandTimeout backstop derived from the method timeout. - 028: negative-path tests (null-gateway, deadline cancellation, parameterization) + e2e Database + WaitForAttribute cases through the real endpoint. - 029: WaitForAttribute is bounded by its WAIT timeout (per-wait CTS + client-abort + explicit token), NOT the method deadline (spec §6) — a long wait may outlive the method timeout; WithRequestAborted threads the raw client-abort token separately. - 030: Central UI compile-surface mirrors (InboundScriptHost / SandboxInboundScriptHost) gained the Database member (drifted since the runtime helper was added) so the authorized async API type-checks at the design-time gate.
This commit is contained in:
@@ -237,12 +237,13 @@ public class InboundScriptExecutor
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(
|
||||
cancellationToken, timeoutCts.Token);
|
||||
|
||||
// IpsenMES MoveIn: expose a read-only Database helper to the script.
|
||||
// Resolve the gateway from a fresh DI scope (declared outside the try so it
|
||||
// lives until after the script handler runs and is disposed here) so a scoped
|
||||
// IDatabaseGateway is honoured. GetService (not GetRequiredService) so a method
|
||||
// that never touches Database still runs even when no IDatabaseGateway is
|
||||
// registered; the helper throws on first use if built without a gateway.
|
||||
// IpsenMES MoveIn: expose the scoped, parameterized Database helper to the script
|
||||
// (reads + writes — InboundAPI-026). Resolve the gateway from a fresh DI scope
|
||||
// (declared outside the try so it lives until after the script handler runs and is
|
||||
// disposed here) so a scoped IDatabaseGateway is honoured. GetService (not
|
||||
// GetRequiredService) so a method that never touches Database still runs even when
|
||||
// no IDatabaseGateway is registered; the helper throws on first use if built
|
||||
// without a gateway.
|
||||
//
|
||||
// A service provider that cannot produce a scope (e.g. a bare test double with
|
||||
// no IServiceScopeFactory) is tolerated: the gateway is simply unavailable, so
|
||||
@@ -265,18 +266,28 @@ public class InboundScriptExecutor
|
||||
try
|
||||
{
|
||||
var gateway = scope?.ServiceProvider.GetService<IDatabaseGateway>();
|
||||
var dbHelper = new InboundDatabaseHelper(gateway, cts.Token);
|
||||
// InboundAPI-027: pass the method timeout so the helper derives a
|
||||
// CommandTimeout backstop and forwards cts.Token to every async DB call —
|
||||
// a slow query is then bounded by the method deadline.
|
||||
var dbHelper = new InboundDatabaseHelper(gateway, cts.Token, timeout);
|
||||
|
||||
// InboundAPI-016: bind the route helper to the method deadline so a
|
||||
// routed Route.To(...).Call(...) inherits the method-level timeout
|
||||
// without the script having to thread the context token by hand.
|
||||
//
|
||||
// InboundAPI-029: also pass the raw request-abort token separately so
|
||||
// Route.To(...).WaitForAttribute(...) can be bounded by its WAIT timeout
|
||||
// (not the generic method deadline) while still being cancelled by a
|
||||
// client disconnect — see RouteTarget.WaitForAttribute.
|
||||
//
|
||||
// Audit Log #23 (ParentExecutionId): also bind the inbound request's
|
||||
// ExecutionId so a routed call carries it as ParentExecutionId — the
|
||||
// spawned site script execution points back at this inbound request.
|
||||
var context = new InboundScriptContext(
|
||||
parameters,
|
||||
route.WithDeadline(cts.Token).WithParentExecutionId(parentExecutionId),
|
||||
route.WithDeadline(cts.Token)
|
||||
.WithRequestAborted(cancellationToken)
|
||||
.WithParentExecutionId(parentExecutionId),
|
||||
dbHelper,
|
||||
cts.Token);
|
||||
|
||||
@@ -382,7 +393,8 @@ public class InboundScriptContext
|
||||
public RouteHelper Route { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Read-only database access for the script (named connections; bound parameters).
|
||||
/// Scoped, parameterized database access for the script (named connections; bound
|
||||
/// parameters; reads and writes — see <see cref="InboundDatabaseHelper"/>).
|
||||
/// </summary>
|
||||
public InboundDatabaseHelper Database { get; }
|
||||
|
||||
@@ -396,7 +408,7 @@ public class InboundScriptContext
|
||||
/// </summary>
|
||||
/// <param name="parameters">The input parameters for the script.</param>
|
||||
/// <param name="route">The route helper for cross-site routing.</param>
|
||||
/// <param name="database">The read-only database helper for the script.</param>
|
||||
/// <param name="database">The scoped, parameterized database helper for the script.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public InboundScriptContext(
|
||||
IReadOnlyDictionary<string, object?> parameters,
|
||||
|
||||
Reference in New Issue
Block a user