feat(security): DashboardTags on API-key constraints; sessions inherit owner tags (SEC-25 groundwork)
Adds a dashboard event-visibility tag to ApiKeyConstraints, riding in the existing constraints JSON blob so no auth-store schema migration is needed (design docs/plans/2026-07-10-dashboard-session-acl-tst15.md sections 3/3.1, open call settled per its own recommendation). The tag is visibility-only: no read, write, browse, or subscribe path consults it, and HasRead/HasWriteConstraints ignore it. GatewaySession gains an immutable, ordinal-ignore-case Tags set stamped at construction from the owning API key, forwarded by MxAccessGatewayService.OpenSession from the resolved ApiKeyIdentity — never from the wire request, so a client cannot label its own session with another tenant's tag. ISessionManager gains a tag-carrying OpenSessionAsync overload whose default implementation forwards to the tagless one, so an implementation that does not model tags opens an untagged (least visible) session. apikey create-key gains --dashboard-tags team-a,team-b (repeatable, trimmed, de-duplicated; an empty segment is rejected rather than dropped) and list-keys prints the tags column. No enforcement yet — the EventsHub ACL that consumes the tag is a later change.
This commit is contained in:
@@ -17,6 +17,33 @@ public interface ISessionManager
|
||||
string? ownerKeyId,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Opens a new gateway session, stamping the owning API key's dashboard event-visibility
|
||||
/// tags onto it (SEC-25).
|
||||
/// </summary>
|
||||
/// <param name="request">Request payload.</param>
|
||||
/// <param name="clientIdentity">Client identity string.</param>
|
||||
/// <param name="ownerKeyId">API key identifier of the caller creating the session.</param>
|
||||
/// <param name="ownerDashboardTags">
|
||||
/// The owner key's <c>ApiKeyConstraints.DashboardTags</c>. Null or empty opens an untagged
|
||||
/// session. Never sourced from the client's wire request — see
|
||||
/// <c>docs/plans/2026-07-10-dashboard-session-acl-tst15.md</c> §3.1.
|
||||
/// </param>
|
||||
/// <param name="cancellationToken">Token to cancel the asynchronous operation.</param>
|
||||
/// <returns>The newly opened session.</returns>
|
||||
/// <remarks>
|
||||
/// The default implementation forwards to the tagless overload, so an implementation that
|
||||
/// does not model tags (unit-test fakes) opens an <em>untagged</em> session. That is the
|
||||
/// fail-closed direction: untagged sessions are the least dashboard-visible ones.
|
||||
/// </remarks>
|
||||
Task<GatewaySession> OpenSessionAsync(
|
||||
SessionOpenRequest request,
|
||||
string? clientIdentity,
|
||||
string? ownerKeyId,
|
||||
IReadOnlyList<string>? ownerDashboardTags,
|
||||
CancellationToken cancellationToken)
|
||||
=> OpenSessionAsync(request, clientIdentity, ownerKeyId, cancellationToken);
|
||||
|
||||
/// <summary>Attempts to retrieve a session by ID.</summary>
|
||||
/// <param name="sessionId">Identifier of the session.</param>
|
||||
/// <param name="session">The retrieved session, if found.</param>
|
||||
|
||||
Reference in New Issue
Block a user