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
@@ -0,0 +1,19 @@
using HotChocolate.Types.Mutable;

namespace HotChocolate.Fusion.Definitions;

internal sealed class ApplyPolicyMutableEnumTypeDefinition : MutableEnumTypeDefinition
{
public ApplyPolicyMutableEnumTypeDefinition()
: base(WellKnownTypeNames.ApplyPolicy)
{
Values.Add(new MutableEnumValue("BEFORE_RESOLVER"));
Values.Add(new MutableEnumValue("AFTER_RESOLVER"));
Values.Add(new MutableEnumValue("VALIDATION"));
}

public static ApplyPolicyMutableEnumTypeDefinition Create()
{
return new ApplyPolicyMutableEnumTypeDefinition();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Mutable;
using DirectiveLocation = HotChocolate.Types.DirectiveLocation;

namespace HotChocolate.Fusion.Definitions;

internal sealed class AuthorizeMutableDirectiveDefinition : MutableDirectiveDefinition
{
public AuthorizeMutableDirectiveDefinition(
MutableScalarTypeDefinition stringType,
MutableEnumTypeDefinition applyPolicyType)
: base(WellKnownDirectiveNames.Authorize)
{
Arguments.Add(new MutableInputFieldDefinition(WellKnownArgumentNames.Policy, stringType));
Arguments.Add(
new MutableInputFieldDefinition(
WellKnownArgumentNames.Roles,
new ListType(new NonNullType(stringType))));
Arguments.Add(
new MutableInputFieldDefinition(WellKnownArgumentNames.Apply, new NonNullType(applyPolicyType))
{
DefaultValue = new EnumValueNode("BEFORE_RESOLVER")
});
IsRepeatable = true;
Locations = DirectiveLocation.FieldDefinition | DirectiveLocation.Object;
}

public static AuthorizeMutableDirectiveDefinition Create(ISchemaDefinition schema)
{
if (!schema.Types.TryGetType<MutableScalarTypeDefinition>(
SpecScalarNames.String.Name,
out var stringType))
{
stringType = BuiltIns.String.Create();
}

if (!schema.Types.TryGetType<MutableEnumTypeDefinition>(
WellKnownTypeNames.ApplyPolicy,
out var applyPolicyType))
{
applyPolicyType = ApplyPolicyMutableEnumTypeDefinition.Create();
}

return new AuthorizeMutableDirectiveDefinition(stringType, applyPolicyType);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System.Collections.Immutable;
using HotChocolate.Fusion.Definitions;
using HotChocolate.Fusion.Directives;
using HotChocolate.Fusion.Extensions;
using HotChocolate.Fusion.Info;
using HotChocolate.Fusion.Options;
using HotChocolate.Language;
using HotChocolate.Types;
using HotChocolate.Types.Mutable;
using ArgumentNames = HotChocolate.Fusion.WellKnownArgumentNames;
using DirectiveNames = HotChocolate.Fusion.WellKnownDirectiveNames;

namespace HotChocolate.Fusion.DirectiveMergers;

internal class AuthorizeDirectiveMerger(DirectiveMergeBehavior mergeBehavior)
: DirectiveMergerBase(mergeBehavior)
{
public override string DirectiveName => DirectiveNames.Authorize;

public override MutableDirectiveDefinition GetCanonicalDirectiveDefinition(MutableSchemaDefinition schema)
{
return AuthorizeMutableDirectiveDefinition.Create(schema);
}

public override void MergeDirectives(
IDirectivesProvider mergedMember,
ImmutableArray<DirectivesProviderInfo> memberDefinitions,
MutableSchemaDefinition mergedSchema)
{
if (!mergedSchema.DirectiveDefinitions.TryGetDirective(DirectiveName, out var directiveDefinition))
{
// Merged definition not found.
return;
}

var uniqueAuthorizeDirectives =
memberDefinitions
.SelectMany(d => d.Member.Directives.Where(dir => dir.Name == DirectiveNames.Authorize))
.Select(AuthorizeDirective.From)
.Distinct(AuthorizeDirectiveEqualityComparer.Instance);

foreach (var authorizeDirective in uniqueAuthorizeDirectives)
{
var arguments = new List<ArgumentAssignment>();

if (authorizeDirective.Policy is not null)
{
arguments.Add(
new ArgumentAssignment(ArgumentNames.Policy, authorizeDirective.Policy));
}

if (authorizeDirective.Roles is not null)
{
arguments.Add(
new ArgumentAssignment(
ArgumentNames.Roles,
new ListValueNode(
authorizeDirective.Roles
.Order(StringComparer.Ordinal)
.Select(r => new StringValueNode(r))
.ToList())));
Comment thread
glen-84 marked this conversation as resolved.
}

if (authorizeDirective.Apply is { } apply and not ApplyPolicy.BeforeResolver)
{
arguments.Add(
new ArgumentAssignment(
ArgumentNames.Apply,
new EnumValueNode(
apply == ApplyPolicy.AfterResolver ? "AFTER_RESOLVER" : "VALIDATION")));
}

mergedMember.AddDirective(new Directive(directiveDefinition, arguments));
}
}

private sealed class AuthorizeDirectiveEqualityComparer
: IEqualityComparer<AuthorizeDirective>
{
public static readonly AuthorizeDirectiveEqualityComparer Instance = new();

public bool Equals(AuthorizeDirective? x, AuthorizeDirective? y)
{
if (ReferenceEquals(x, y))
{
return true;
}

if (x is null || y is null)
{
return false;
}

return x.Policy == y.Policy
&& (x.Apply ?? ApplyPolicy.BeforeResolver) == (y.Apply ?? ApplyPolicy.BeforeResolver)
&& RolesEqual(x.Roles, y.Roles);
}

public int GetHashCode(AuthorizeDirective obj)
{
var hash = new HashCode();
hash.Add(obj.Policy);
hash.Add(obj.Apply ?? ApplyPolicy.BeforeResolver);

if (obj.Roles is not null)
{
foreach (var role in obj.Roles.Order(StringComparer.Ordinal))
{
hash.Add(role);
}
}

return hash.ToHashCode();
}

private static bool RolesEqual(List<string>? a, List<string>? b)
{
if (a is null && b is null)
{
return true;
}

if (a is null || b is null)
{
return false;
}

if (a.Count != b.Count)
{
return false;
}

return a.Order(StringComparer.Ordinal).SequenceEqual(b.Order(StringComparer.Ordinal));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace HotChocolate.Fusion.Directives;

internal enum ApplyPolicy
{
BeforeResolver = 0,
AfterResolver = 1,
Validation = 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using HotChocolate.Language;
using HotChocolate.Types;
using static HotChocolate.Fusion.Properties.CompositionResources;
using ArgumentNames = HotChocolate.Fusion.WellKnownArgumentNames;

namespace HotChocolate.Fusion.Directives;

internal sealed class AuthorizeDirective(
string? policy,
List<string>? roles,
ApplyPolicy? apply = null)
{
public string? Policy { get; } = policy;

public List<string>? Roles { get; } = roles;

public ApplyPolicy? Apply { get; } = apply;

public static AuthorizeDirective From(IDirective directive)
{
string? policy = null;
List<string>? roles = null;
ApplyPolicy? applyPolicy = null;

if (directive.Arguments.TryGetValue(ArgumentNames.Policy, out var policyArg))
{
policy = policyArg switch
{
StringValueNode stringValueNode => stringValueNode.Value,
NullValueNode => null,
_ => throw new InvalidOperationException(AuthorizeDirective_PolicyArgument_Invalid)
};
}

if (directive.Arguments.TryGetValue(ArgumentNames.Roles, out var rolesArg))
{
roles = rolesArg switch
{
ListValueNode listValueNode when listValueNode.Items.All(v => v is StringValueNode)
=> listValueNode.Items.Cast<StringValueNode>().Select(v => v.Value).ToList(),
NullValueNode => null,
_ => throw new InvalidOperationException(AuthorizeDirective_RolesArgument_Invalid)
};
}

if (directive.Arguments.TryGetValue(ArgumentNames.Apply, out var applyArg))
{
applyPolicy = applyArg switch
{
EnumValueNode enumValueNode => GetApplyPolicy(enumValueNode.Value),
_ => throw new InvalidOperationException(AuthorizeDirective_ApplyArgument_Invalid)
};
}

return new AuthorizeDirective(policy, roles, applyPolicy);
Comment thread
glen-84 marked this conversation as resolved.
}

private static ApplyPolicy GetApplyPolicy(string applyPolicyValue)
{
return applyPolicyValue switch
{
"BEFORE_RESOLVER" => ApplyPolicy.BeforeResolver,
"AFTER_RESOLVER" => ApplyPolicy.AfterResolver,
"VALIDATION" => ApplyPolicy.Validation,
_ => throw new InvalidOperationException(
string.Format(AuthorizeDirective_ApplyArgument_InvalidEnumValue, applyPolicyValue))
};
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AuthorizeDirective_ApplyArgument_Invalid" xml:space="preserve">
<value>The 'apply' argument of the @authorize directive must be of type ApplyPolicy.</value>
</data>
<data name="AuthorizeDirective_ApplyArgument_InvalidEnumValue" xml:space="preserve">
<value>The value '{0}' for argument 'apply' in the @authorize directive is invalid.</value>
</data>
<data name="AuthorizeDirective_PolicyArgument_Invalid" xml:space="preserve">
<value>The 'policy' argument of the @authorize directive must be of type String.</value>
</data>
<data name="AuthorizeDirective_RolesArgument_Invalid" xml:space="preserve">
<value>The 'roles' argument of the @authorize directive must be of type [String!].</value>
</data>
<data name="CompositionLogExtensions_EntryMessageWithSchemaName" xml:space="preserve">
<value>{0} (Schema: '{1}')</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public SourceSchemaMerger(
_directiveMergers =
new Dictionary<string, IDirectiveMerger>
{
{
DirectiveNames.Authorize,
new AuthorizeDirectiveMerger(DirectiveMergeBehavior.Include)
},
{
DirectiveNames.CacheControl,
new CacheControlDirectiveMerger(_options.CacheControlMergeBehavior)
Expand Down Expand Up @@ -738,6 +742,8 @@ private MutableInterfaceTypeDefinition MergeInterfaceTypes(
() =>
{
var memberDefinitions = typeGroup.Select(g => new DirectivesProviderInfo(g.Type, g.Schema)).ToImmutableArray();
_directiveMergers[DirectiveNames.Authorize]
.MergeDirectives(objectType, memberDefinitions, mergedSchema);
_directiveMergers[DirectiveNames.CacheControl]
.MergeDirectives(objectType, memberDefinitions, mergedSchema);
_directiveMergers[DirectiveNames.Cost]
Expand Down Expand Up @@ -862,6 +868,8 @@ private MutableInterfaceTypeDefinition MergeInterfaceTypes(
{
var memberDefinitions =
fieldGroup.Select(g => new DirectivesProviderInfo(g.Field, g.Schema)).ToImmutableArray();
_directiveMergers[DirectiveNames.Authorize]
.MergeDirectives(outputField, memberDefinitions, mergedSchema);
_directiveMergers[DirectiveNames.CacheControl]
.MergeDirectives(outputField, memberDefinitions, mergedSchema);
_directiveMergers[DirectiveNames.Cost]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace HotChocolate.Fusion;

internal static class WellKnownArgumentNames
{
public const string Apply = "apply";
public const string AssumedSize = "assumedSize";
public const string DestructiveHint = "destructiveHint";
public const string Field = "field";
Expand All @@ -21,9 +22,11 @@ internal static class WellKnownArgumentNames
public const string Partial = "partial";
public const string Path = "path";
public const string Pattern = "pattern";
public const string Policy = "policy";
public const string Provides = "provides";
public const string Requirements = "requirements";
public const string RequireOneSlicingArgument = "requireOneSlicingArgument";
public const string Roles = "roles";
public const string Schema = "schema";
public const string Scope = "scope";
public const string SharedMaxAge = "sharedMaxAge";
Expand Down
Loading
Loading