f40409a688
Tracking IDs are review bookkeeping that rots in shipped source; the rationale they tagged is kept. Comment-only change (CommentChecker 458 -> 16 false positives).
73 lines
4.1 KiB
C#
73 lines
4.1 KiB
C#
using Grpc.Core;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.GalaxyRepository.Grpc;
|
|
using ZB.MOM.WW.MxGateway.Server.Configuration;
|
|
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
|
|
|
|
namespace ZB.MOM.WW.MxGateway.Server.Security.Authorization;
|
|
|
|
/// <summary>Scopes Galaxy browse results to the calling API key's BrowseSubtrees constraint.</summary>
|
|
/// <remarks>
|
|
/// Distinguishes two states the earlier code collapsed into one: "authenticated, no
|
|
/// BrowseSubtrees constraint" (legitimately unscoped — the common case) and "no ambient identity
|
|
/// at all" (the interceptor-ordering invariant below is broken). The second used to resolve to the
|
|
/// full hierarchy silently, which made a scoping bypass invisible; it now denies loudly.
|
|
/// </remarks>
|
|
/// <param name="identityAccessor">Accessor for the ambient per-call API-key identity.</param>
|
|
/// <param name="options">Gateway options supplying <c>MxGateway:Authentication:Mode</c>.</param>
|
|
/// <param name="logger">Logger used to name the broken invariant when identity is absent.</param>
|
|
public sealed class GatewayBrowseScopeProvider(
|
|
IGatewayRequestIdentityAccessor identityAccessor,
|
|
IOptions<GatewayOptions> options,
|
|
ILogger<GatewayBrowseScopeProvider> logger)
|
|
: IGalaxyBrowseScopeProvider
|
|
{
|
|
/// <summary>Resolves the Galaxy browse subtrees the calling API key is scoped to, from the ambient request identity.</summary>
|
|
/// <param name="context">The gRPC server call context for the in-flight browse request.</param>
|
|
/// <returns>The caller's configured browse subtrees, or <see langword="null"/>/empty when the caller is unscoped (full hierarchy).</returns>
|
|
/// <exception cref="RpcException">
|
|
/// <see cref="StatusCode.PermissionDenied"/> when API-key authentication is enabled but no
|
|
/// ambient identity is present — the invariant described below has been broken and the
|
|
/// request's scope cannot be established.
|
|
/// </exception>
|
|
public IReadOnlyList<string>? ResolveBrowseSubtrees(ServerCallContext context)
|
|
{
|
|
// Invariant: the caller identity is the ambient one pushed by
|
|
// GatewayGrpcAuthorizationInterceptor earlier in this same call. That interceptor is
|
|
// registered globally and therefore runs before the library's galaxy service, so
|
|
// Current is populated for any authenticated galaxy RPC. Do not reorder the interceptor
|
|
// without revisiting this.
|
|
ApiKeyIdentity? identity = identityAccessor.Current;
|
|
if (identity is not null)
|
|
{
|
|
return identity.EffectiveConstraints.BrowseSubtrees;
|
|
}
|
|
|
|
// Mode=Disabled is the one configuration in which identity-absent is expected rather than
|
|
// broken: nothing authenticates, so no key and no constraints exist to scope by, and the
|
|
// deployment has already accepted an unauthenticated gRPC surface. Empty BrowseSubtrees is
|
|
// what the library reads as "no scoping".
|
|
if (options.Value.Authentication.Mode == AuthenticationMode.Disabled)
|
|
{
|
|
return ApiKeyConstraints.Empty.BrowseSubtrees;
|
|
}
|
|
|
|
// Authentication is on, so an identity MUST have been pushed before the galaxy service ran.
|
|
// Failing open here would hand a browse-scoped key the full hierarchy with nothing logged
|
|
// and nothing failing — a constraint-narrowing control's absence is invisible in normal
|
|
// operation. Deny instead, and name the invariant so the misconfiguration is diagnosable
|
|
// from one log line rather than from a scoping audit nobody thought to run.
|
|
logger.LogWarning(
|
|
"Galaxy browse scope could not be resolved for {Method}: no ambient API-key identity "
|
|
+ "while Authentication:Mode is {Mode}. GatewayGrpcAuthorizationInterceptor must run "
|
|
+ "before the Galaxy service on every call; the request is denied rather than served "
|
|
+ "unscoped.",
|
|
context.Method,
|
|
options.Value.Authentication.Mode);
|
|
|
|
throw new RpcException(new Status(
|
|
StatusCode.PermissionDenied,
|
|
"Browse scope could not be established for this request."));
|
|
}
|
|
}
|