GQL Authorization Middleware implemented, will take care of controllers next.
This commit is contained in:
parent
45279da02b
commit
3e6c76ab18
31
Demo/Security/CommandAndQueryAuthorizationService.cs
Normal file
31
Demo/Security/CommandAndQueryAuthorizationService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,8 @@ using PoweredSoft.Data.Core;
|
|||||||
using PoweredSoft.DynamicQuery;
|
using PoweredSoft.DynamicQuery;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using PoweredSoft.CQRS.GraphQL.HotChocolate.DynamicQuery;
|
using PoweredSoft.CQRS.GraphQL.HotChocolate.DynamicQuery;
|
||||||
|
using PoweredSoft.CQRS.Abstractions.Security;
|
||||||
|
using Demo.Security;
|
||||||
|
|
||||||
namespace Demo
|
namespace Demo
|
||||||
{
|
{
|
||||||
@ -41,6 +43,9 @@ namespace Demo
|
|||||||
AddDynamicQueries(services);
|
AddDynamicQueries(services);
|
||||||
AddCommands(services);
|
AddCommands(services);
|
||||||
|
|
||||||
|
services.AddHttpContextAccessor();
|
||||||
|
services.AddTransient<IQueryAuthorizationService, CommandAndQueryAuthorizationService>();
|
||||||
|
services.AddTransient<ICommandAuthorizationService, CommandAndQueryAuthorizationService>();
|
||||||
services.AddTransient<IAsyncQueryableHandlerService, InMemoryQueryableHandler>();
|
services.AddTransient<IAsyncQueryableHandlerService, InMemoryQueryableHandler>();
|
||||||
services.AddPoweredSoftDataServices();
|
services.AddPoweredSoftDataServices();
|
||||||
services.AddPoweredSoftDynamicQuery();
|
services.AddPoweredSoftDynamicQuery();
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace PoweredSoft.CQRS.Abstractions.Security
|
||||||
|
{
|
||||||
|
public enum AuthorizationResult
|
||||||
|
{
|
||||||
|
Unauthorized,
|
||||||
|
Forbidden,
|
||||||
|
Allowed
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -61,6 +61,9 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate.DynamicQuery
|
|||||||
|
|
||||||
f.Type(resultType);
|
f.Type(resultType);
|
||||||
|
|
||||||
|
// security middleware
|
||||||
|
f.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
|
||||||
|
|
||||||
// middleware to validate.
|
// middleware to validate.
|
||||||
f.Use<QueryValidationMiddleware>();
|
f.Use<QueryValidationMiddleware>();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@ using HotChocolate.Types;
|
|||||||
using PoweredSoft.CQRS.Abstractions;
|
using PoweredSoft.CQRS.Abstractions;
|
||||||
using PoweredSoft.CQRS.Abstractions.Discovery;
|
using PoweredSoft.CQRS.Abstractions.Discovery;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
||||||
@ -28,11 +30,25 @@ namespace PoweredSoft.CQRS.GraphQL.HotChocolate
|
|||||||
var queryField = desc.Field(q.LowerCamelCaseName);
|
var queryField = desc.Field(q.LowerCamelCaseName);
|
||||||
var typeToGet = typeof(IQueryHandler<,>).MakeGenericType(q.QueryType, q.QueryResultType);
|
var typeToGet = typeof(IQueryHandler<,>).MakeGenericType(q.QueryType, q.QueryResultType);
|
||||||
|
|
||||||
queryField.Type(q.QueryResultType);
|
queryField.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
|
||||||
|
|
||||||
// TODO.
|
// if its a IQueryable.
|
||||||
// always required.
|
if (q.QueryResultType.Namespace == "System.Linq" && q.QueryResultType.Name.Contains("IQueryable"))
|
||||||
//queryField.Use((sp, d) => new QueryAuthorizationMiddleware(q.QueryType, d));
|
{
|
||||||
|
//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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (q.QueryType.GetProperties().Length == 0)
|
if (q.QueryType.GetProperties().Length == 0)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user