GQL Authorization Middleware implemented, will take care of controllers next.

This commit is contained in:
David Lebee 2021-02-04 21:00:24 -05:00
parent 45279da02b
commit 3e6c76ab18
9 changed files with 183 additions and 4 deletions

View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Http;
using PoweredSoft.CQRS.Abstractions.Security;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Demo.Security
{
public class CommandAndQueryAuthorizationService : IQueryAuthorizationService, ICommandAuthorizationService
{
private readonly IHttpContextAccessor httpContextAccessor;
public CommandAndQueryAuthorizationService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public Task<AuthorizationResult> IsAllowedAsync(Type queryOrCommandType, CancellationToken cancellationToken = default)
{
var authResult = httpContextAccessor.HttpContext.Request.Query["auth-result"].FirstOrDefault();
if (authResult == "Unauthorized")
return Task.FromResult(AuthorizationResult.Unauthorized);
else if (authResult == "Forbidden")
return Task.FromResult(AuthorizationResult.Forbidden);
return Task.FromResult(AuthorizationResult.Allowed);
}
}
}

View File

@ -22,6 +22,8 @@ using PoweredSoft.Data.Core;
using PoweredSoft.DynamicQuery;
using System.Linq;
using PoweredSoft.CQRS.GraphQL.HotChocolate.DynamicQuery;
using PoweredSoft.CQRS.Abstractions.Security;
using Demo.Security;
namespace Demo
{
@ -41,6 +43,9 @@ namespace Demo
AddDynamicQueries(services);
AddCommands(services);
services.AddHttpContextAccessor();
services.AddTransient<IQueryAuthorizationService, CommandAndQueryAuthorizationService>();
services.AddTransient<ICommandAuthorizationService, CommandAndQueryAuthorizationService>();
services.AddTransient<IAsyncQueryableHandlerService, InMemoryQueryableHandler>();
services.AddPoweredSoftDataServices();
services.AddPoweredSoftDynamicQuery();

View File

@ -0,0 +1,9 @@
namespace PoweredSoft.CQRS.Abstractions.Security
{
public enum AuthorizationResult
{
Unauthorized,
Forbidden,
Allowed
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PoweredSoft.CQRS.Abstractions.Security
{
public interface ICommandAuthorizationService
{
Task<AuthorizationResult> IsAllowedAsync(Type commandType, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PoweredSoft.CQRS.Abstractions.Security
{
public interface IQueryAuthorizationService
{
Task<AuthorizationResult> IsAllowedAsync(Type queryType, CancellationToken cancellationToken = default);
}
}

View File

@ -61,6 +61,9 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate.DynamicQuery
f.Type(resultType);
// security middleware
f.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
// middleware to validate.
f.Use<QueryValidationMiddleware>();

View File

@ -0,0 +1,46 @@
using HotChocolate;
using HotChocolate.Resolvers;
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.CQRS.Abstractions.Security;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
{
internal class MutationAuthorizationMiddleware
{
private readonly Type mutationType;
private readonly FieldDelegate _next;
public MutationAuthorizationMiddleware(Type mutationType,FieldDelegate next)
{
this.mutationType = mutationType;
_next = next;
}
public async Task InvokeAsync(IMiddlewareContext context)
{
var mutationAuthorizationService = context.Service<IServiceProvider>().GetService<ICommandAuthorizationService>();
if (mutationAuthorizationService != null)
{
var authorizationResult = await mutationAuthorizationService.IsAllowedAsync(mutationType);
if (authorizationResult != AuthorizationResult.Allowed)
{
var eb = ErrorBuilder.New()
.SetMessage(authorizationResult == AuthorizationResult.Unauthorized ? "Unauthorized" : "Forbidden")
.SetCode("AuthorizationResult")
.SetExtension("StatusCode", authorizationResult == AuthorizationResult.Unauthorized ? "401" : "403")
.SetPath(context.Path)
.AddLocation(context.Selection.SyntaxNode);
context.Result = eb.Build();
return;
}
}
await _next.Invoke(context);
}
}
}

View File

@ -0,0 +1,46 @@
using HotChocolate;
using HotChocolate.Resolvers;
using Microsoft.Extensions.DependencyInjection;
using PoweredSoft.CQRS.Abstractions.Security;
using System;
using System.Threading.Tasks;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
{
public class QueryAuthorizationMiddleware
{
private readonly Type queryType;
private readonly FieldDelegate _next;
public QueryAuthorizationMiddleware(Type queryType, FieldDelegate next)
{
this.queryType = queryType;
_next = next;
}
public async Task InvokeAsync(IMiddlewareContext context)
{
var queryAuthorizationService = context.Service<IServiceProvider>().GetService<IQueryAuthorizationService>();
if (queryAuthorizationService != null)
{
var authorizationResult = await queryAuthorizationService.IsAllowedAsync(queryType);
if (authorizationResult != AuthorizationResult.Allowed)
{
var eb = ErrorBuilder.New()
.SetMessage(authorizationResult == AuthorizationResult.Unauthorized ? "Unauthorized" : "Forbidden")
.SetCode("AuthorizationResult")
.SetExtension("StatusCode", authorizationResult == AuthorizationResult.Unauthorized ? "401" : "403")
.SetPath(context.Path)
.AddLocation(context.Selection.SyntaxNode);
context.Result = eb.Build();
return;
}
}
await _next.Invoke(context);
}
}
}

View File

@ -4,6 +4,8 @@ using HotChocolate.Types;
using PoweredSoft.CQRS.Abstractions;
using PoweredSoft.CQRS.Abstractions.Discovery;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
@ -28,11 +30,25 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
var queryField = desc.Field(q.LowerCamelCaseName);
var typeToGet = typeof(IQueryHandler<,>).MakeGenericType(q.QueryType, q.QueryResultType);
queryField.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
// if its a IQueryable.
if (q.QueryResultType.Namespace == "System.Linq" && q.QueryResultType.Name.Contains("IQueryable"))
{
//waiting on answer to be determined.
/*var genericArgument = q.QueryResultType.GetGenericArguments().First();
var type = new ListType(new NonNullType(new NamedTypeNode));
queryField.Type(type);
queryField.UsePaging();
*/
queryField.Type(q.QueryResultType);
}
else
{
queryField.Type(q.QueryResultType);
// TODO.
// always required.
//queryField.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
}
if (q.QueryType.GetProperties().Length == 0)
{