Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,47 @@ public override void OnBeforeCompleteMetadata(
}
}

public override void OnBeforeMakeExecutable(
ITypeCompletionContext completionContext,
TypeSystemConfiguration configuration)
{
if (completionContext.Type is not ObjectType objectType
|| configuration is not ObjectTypeConfiguration typeDef)
{
return;
}

foreach (var fieldDef in typeDef.Fields)
{
if (fieldDef.BatchResolver is null
|| !objectType.Fields.TryGetField(fieldDef.Name, out var field))
{
continue;
}

var directives = field.Directives;
var length = directives.Count;
ref var start = ref directives.GetReference();

for (var i = length - 1; i >= 0; i--)
{
var directive = Unsafe.Add(ref start, i);

if (directive.Type.Name.EqualsOrdinal(Authorize))
{
var authDir = directive.ToValue<AuthorizeDirective>();

// field directives go in front of the type level authorization,
// in the same order as in the field pipeline.
fieldDef.BatchMiddlewareConfigurations.Insert(
0,
CreateBatchAuthMiddleware(
authDir));
}
}
}
}

public override void OnAfterMakeExecutable()
{
foreach (var type in _objectTypes)
Expand Down Expand Up @@ -411,6 +452,7 @@ private void ApplyAuthMiddleware(
bool isNodeField)
{
var insertPos = 0;
var batchInsertPos = 0;

// we try to locate the last auth middleware and insert after it.
if (fieldDef.MiddlewareConfigurations.Count > 0)
Expand All @@ -428,6 +470,22 @@ private void ApplyAuthMiddleware(
}
}

// we do the same for the batch pipeline.
if (fieldDef.BatchResolver is not null)
{
for (var i = 0; i < fieldDef.BatchMiddlewareConfigurations.Count; i++)
{
if (fieldDef.BatchMiddlewareConfigurations[i].Key == WellKnownMiddleware.Authorization)
{
batchInsertPos = i + 1;
}
else
{
break;
}
}
}

// next we get the collection of directives for the object type that needs to be secured.
var directives = GetOrCreateDirectives(authTypeReg);
var length = directives.Count;
Expand Down Expand Up @@ -461,6 +519,14 @@ private void ApplyAuthMiddleware(
insertPos++,
CreateAuthMiddleware(
authDir));

if (fieldDef.BatchResolver is not null)
{
fieldDef.BatchMiddlewareConfigurations.Insert(
batchInsertPos++,
CreateBatchAuthMiddleware(
authDir));
}
}
}
}
Expand All @@ -482,6 +548,13 @@ private static FieldMiddlewareConfiguration CreateAuthMiddleware(
isRepeatable: true,
key: WellKnownMiddleware.Authorization);

private static BatchFieldMiddlewareConfiguration CreateBatchAuthMiddleware(
AuthorizeDirective directive)
=> new BatchFieldMiddlewareConfiguration(
next => new AuthorizeBatchMiddleware(next, directive).InvokeAsync,
isRepeatable: true,
key: WellKnownMiddleware.Authorization);

private DirectiveCollection GetOrCreateDirectives(RegisteredType registration)
{
var type = (ObjectType)registration.Type;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Collections.Immutable;
using HotChocolate.Resolvers;

namespace HotChocolate.Authorization;

internal sealed class AuthorizeBatchMiddleware(
BatchFieldDelegate next,
AuthorizeDirective directive)
{
private readonly BatchFieldDelegate _next = next ??
throw new ArgumentNullException(nameof(next));
private readonly AuthorizeDirective _directive = directive ??
throw new ArgumentNullException(nameof(directive));

public async ValueTask InvokeAsync(ImmutableArray<IMiddlewareContext> contexts)
{
switch (_directive.Apply)
{
case ApplyPolicy.AfterResolver:
{
await _next(contexts).ConfigureAwait(false);

foreach (var context in contexts)
{
if (context.Result is null)
{
continue;
}

var handler = context.GetAuthorizationHandler();
var state = await handler.AuthorizeAsync(context, _directive).ConfigureAwait(false);

if (state != AuthorizeResult.Allowed && !AuthorizeMiddleware.IsErrorResult(context))
{
AuthorizeMiddleware.SetError(context, _directive, state);
}
}
break;
}

case ApplyPolicy.BeforeResolver:
{
// the authorization result can depend on the parent object, so each context
// is authorized on its own and only the allowed ones are passed on to the resolver.
ImmutableArray<IMiddlewareContext>.Builder? allowed = null;

for (var i = 0; i < contexts.Length; i++)
{
var context = contexts[i];
var handler = context.GetAuthorizationHandler();
var state = await handler.AuthorizeAsync(context, _directive).ConfigureAwait(false);

if (state == AuthorizeResult.Allowed)
{
allowed?.Add(context);
continue;
}

if (allowed is null)
{
allowed = ImmutableArray.CreateBuilder<IMiddlewareContext>(contexts.Length - 1);
allowed.AddRange(contexts, i);
}

AuthorizeMiddleware.SetError(context, _directive, state);
}

if (allowed is null)
{
await _next(contexts).ConfigureAwait(false);
}
else if (allowed.Count > 0)
{
await _next(allowed.ToImmutable()).ConfigureAwait(false);
}
break;
}

default:
await _next(contexts).ConfigureAwait(false);
break;
}
}
}
11 changes: 6 additions & 5 deletions src/HotChocolate/Core/src/Authorization/AuthorizeMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task InvokeAsync(IMiddlewareContext context)

if (state != AuthorizeResult.Allowed && !IsErrorResult(context))
{
SetError(context, state);
SetError(context, _directive, state);
}
}
break;
Expand All @@ -45,7 +45,7 @@ public async Task InvokeAsync(IMiddlewareContext context)
}
else
{
SetError(context, state);
SetError(context, _directive, state);
}
break;
}
Expand All @@ -56,11 +56,12 @@ public async Task InvokeAsync(IMiddlewareContext context)
}
}

private static bool IsErrorResult(IMiddlewareContext context)
internal static bool IsErrorResult(IMiddlewareContext context)
=> context.Result is IError or IEnumerable<IError>;

private void SetError(
internal static void SetError(
IMiddlewareContext context,
AuthorizeDirective directive,
AuthorizeResult state)
=> context.Result = state switch
{
Expand All @@ -74,7 +75,7 @@ private void SetError(
=> ErrorBuilder.New()
.SetMessage(
AuthorizeMiddleware_PolicyNotFound,
_directive.Policy)
directive.Policy)
.SetCode(ErrorCodes.Authentication.PolicyNotFound)
.SetPath(context.Path)
.Build(),
Expand Down
Loading
Loading