using Grpc.Core; using Grpc.Core.Interceptors; using Microsoft.Extensions.Options; using MxGateway.Server.Configuration; using MxGateway.Server.Security.Authentication; namespace MxGateway.Server.Security.Authorization; public sealed class GatewayGrpcAuthorizationInterceptor( IApiKeyVerifier apiKeyVerifier, GatewayGrpcScopeResolver scopeResolver, IGatewayRequestIdentityAccessor identityAccessor, IOptions options) : Interceptor { public override async Task UnaryServerHandler( TRequest request, ServerCallContext context, UnaryServerMethod continuation) { ApiKeyIdentity? identity = await AuthenticateAndAuthorizeAsync(request, context).ConfigureAwait(false); IDisposable? identityScope = identity is null ? null : identityAccessor.Push(identity); using (identityScope) { return await continuation(request, context).ConfigureAwait(false); } } public override async Task ServerStreamingServerHandler( TRequest request, IServerStreamWriter responseStream, ServerCallContext context, ServerStreamingServerMethod continuation) { ApiKeyIdentity? identity = await AuthenticateAndAuthorizeAsync(request, context).ConfigureAwait(false); IDisposable? identityScope = identity is null ? null : identityAccessor.Push(identity); using (identityScope) { await continuation(request, responseStream, context).ConfigureAwait(false); } } private async Task AuthenticateAndAuthorizeAsync( TRequest request, ServerCallContext context) where TRequest : class { if (options.Value.Authentication.Mode == AuthenticationMode.Disabled) { return null; } string? authorizationHeader = context.RequestHeaders.GetValue("authorization"); ApiKeyVerificationResult verificationResult = await apiKeyVerifier .VerifyAsync(authorizationHeader, context.CancellationToken) .ConfigureAwait(false); if (!verificationResult.Succeeded || verificationResult.Identity is null) { throw new RpcException(new Status( StatusCode.Unauthenticated, "Missing or invalid API key.")); } string requiredScope = scopeResolver.ResolveRequiredScope(request); if (!verificationResult.Identity.Scopes.Contains(requiredScope)) { throw new RpcException(new Status( StatusCode.PermissionDenied, $"API key is missing required scope '{requiredScope}'.")); } return verificationResult.Identity; } }