Compare commits

...

2 Commits

Author SHA1 Message Date
david.nguyen 18f81a28e8 Add authorization checks to gRPC service implementations
Publish NuGets / build (release) Successful in 44s
- Add ICommandAuthorizationService check to CommandServiceImpl
- Add IQueryAuthorizationService check to QueryServiceImpl
- Add IQueryAuthorizationService check to DynamicQueryServiceImpl
- Return Unauthenticated/PermissionDenied gRPC status codes
- Use global:: prefix for Grpc.Core namespace to avoid conflicts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 14:18:07 -05:00
david.nguyen 201768e716 Revert AllowAnonymous endpoint propagation
Publish NuGets / build (release) Successful in 35s
Remove the WithAllowAnonymousIfAttributePresent helper method.
Authorization should be handled by IQueryAuthorizationService and
ICommandAuthorizationService implementations, not by ASP.NET Core
middleware.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 13:07:03 -05:00
2 changed files with 40 additions and 18 deletions
@@ -2291,6 +2291,7 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine("using Google.Protobuf.WellKnownTypes;");
sb.AppendLine($"using {rootNamespace}.Grpc;");
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
sb.AppendLine("using Svrnty.CQRS.Abstractions.Security;");
sb.AppendLine();
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
@@ -2321,6 +2322,17 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine(" using var scope = _scopeFactory.CreateScope();");
sb.AppendLine(" var serviceProvider = scope.ServiceProvider;");
sb.AppendLine();
sb.AppendLine(" // Authorization check");
sb.AppendLine($" var authorizationService = serviceProvider.GetService<ICommandAuthorizationService>();");
sb.AppendLine(" if (authorizationService != null)");
sb.AppendLine(" {");
sb.AppendLine($" var authResult = await authorizationService.IsAllowedAsync(typeof({command.FullyQualifiedName}), context.CancellationToken);");
sb.AppendLine(" if (authResult == AuthorizationResult.Unauthorized)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unauthenticated, \"Unauthorized\"));");
sb.AppendLine(" if (authResult == AuthorizationResult.Forbidden)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.PermissionDenied, \"Forbidden\"));");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" var command = new {command.FullyQualifiedName}");
sb.AppendLine(" {");
foreach (var prop in command.Properties)
@@ -2425,6 +2437,7 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
sb.AppendLine($"using {rootNamespace}.Grpc;");
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
sb.AppendLine("using Svrnty.CQRS.Abstractions.Security;");
sb.AppendLine();
sb.AppendLine($"namespace {rootNamespace}.Grpc.Services");
@@ -2455,6 +2468,17 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine(" using var scope = _scopeFactory.CreateScope();");
sb.AppendLine(" var serviceProvider = scope.ServiceProvider;");
sb.AppendLine();
sb.AppendLine(" // Authorization check");
sb.AppendLine($" var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();");
sb.AppendLine(" if (authorizationService != null)");
sb.AppendLine(" {");
sb.AppendLine($" var authResult = await authorizationService.IsAllowedAsync(typeof({query.FullyQualifiedName}), context.CancellationToken);");
sb.AppendLine(" if (authResult == AuthorizationResult.Unauthorized)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unauthenticated, \"Unauthorized\"));");
sb.AppendLine(" if (authResult == AuthorizationResult.Forbidden)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.PermissionDenied, \"Forbidden\"));");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine($" var handler = serviceProvider.GetRequiredService<{query.HandlerInterfaceName}>();");
sb.AppendLine($" var query = new {query.FullyQualifiedName}");
sb.AppendLine(" {");
@@ -2736,6 +2760,7 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
sb.AppendLine($"using {rootNamespace}.Grpc;");
sb.AppendLine("using Svrnty.CQRS.Abstractions;");
sb.AppendLine("using Svrnty.CQRS.Abstractions.Security;");
sb.AppendLine("using Svrnty.CQRS.DynamicQuery.Abstractions;");
sb.AppendLine("using PoweredSoft.DynamicQuery.Core;");
sb.AppendLine();
@@ -2768,6 +2793,17 @@ namespace Svrnty.CQRS.Grpc.Generators
sb.AppendLine(" using var scope = _scopeFactory.CreateScope();");
sb.AppendLine(" var serviceProvider = scope.ServiceProvider;");
sb.AppendLine();
sb.AppendLine(" // Authorization check");
sb.AppendLine($" var authorizationService = serviceProvider.GetService<IQueryAuthorizationService>();");
sb.AppendLine(" if (authorizationService != null)");
sb.AppendLine(" {");
sb.AppendLine($" var authResult = await authorizationService.IsAllowedAsync(typeof({dynamicQuery.QueryInterfaceName}), context.CancellationToken);");
sb.AppendLine(" if (authResult == AuthorizationResult.Unauthorized)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unauthenticated, \"Unauthorized\"));");
sb.AppendLine(" if (authResult == AuthorizationResult.Forbidden)");
sb.AppendLine(" throw new RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.PermissionDenied, \"Forbidden\"));");
sb.AppendLine(" }");
sb.AppendLine();
// Build the dynamic query object
if (dynamicQuery.HasParams)
@@ -83,8 +83,7 @@ public static class EndpointRouteBuilderExtensions
.Produces(200, queryMeta.QueryResultType)
.Produces(400)
.Produces(401)
.Produces(403)
.WithAllowAnonymousIfAttributePresent(queryMeta.QueryType);
.Produces(403);
}
private static void MapQueryGet(
@@ -147,8 +146,7 @@ public static class EndpointRouteBuilderExtensions
.Produces(200, queryMeta.QueryResultType)
.Produces(400)
.Produces(401)
.Produces(403)
.WithAllowAnonymousIfAttributePresent(queryMeta.QueryType);
.Produces(403);
}
public static IEndpointRouteBuilder MapSvrntyCommands(this IEndpointRouteBuilder endpoints, string routePrefix = "api/command")
@@ -215,8 +213,7 @@ public static class EndpointRouteBuilderExtensions
.Produces(200)
.Produces(400)
.Produces(401)
.Produces(403)
.WithAllowAnonymousIfAttributePresent(commandMeta.CommandType);
.Produces(403);
}
private static void MapCommandWithResult(
@@ -263,17 +260,6 @@ public static class EndpointRouteBuilderExtensions
.Produces(200, commandMeta.CommandResultType)
.Produces(400)
.Produces(401)
.Produces(403)
.WithAllowAnonymousIfAttributePresent(commandMeta.CommandType);
}
private static RouteHandlerBuilder WithAllowAnonymousIfAttributePresent(this RouteHandlerBuilder builder, Type type)
{
var allowAnonymousAttribute = type.GetCustomAttribute<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>();
if (allowAnonymousAttribute != null)
{
builder.AllowAnonymous();
}
return builder;
.Produces(403);
}
}