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 @@ -197,6 +197,46 @@ private static ImmutableArray<ImmutableArray<string>> Minimize(
return [.. kept];
}

/// <summary>
/// Determines whether every alternative group of <paramref name="requirement"/> contains an
/// alternative group of <paramref name="dependency"/>. An empty dependency is always covered.
/// </summary>
public static bool Covers(
ImmutableArray<ImmutableArray<string>> requirement,
ImmutableArray<ImmutableArray<string>> dependency)
{
if (dependency.IsEmpty)
{
return true;
}

if (requirement.IsEmpty)
{
return false;
}

foreach (var group in requirement)
{
var covered = false;

foreach (var dependencyGroup in dependency)
{
if (IsSubset(dependencyGroup, group))
{
covered = true;
break;
}
}

if (!covered)
{
return false;
}
}

return true;
}

// Both groups are sorted and deduplicated.
private static bool IsSubset(ImmutableArray<string> subset, ImmutableArray<string> superset)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
using DirectiveNames = HotChocolate.Fusion.WellKnownDirectiveNames;

namespace HotChocolate.Fusion.Directives;

Expand Down Expand Up @@ -68,6 +69,32 @@ public bool Matches(MergedAuthorization other)
&& GroupsMatch(Policies, other.Policies);
}

/// <summary>
/// Gets the names of the directives of <paramref name="dependency"/> that this requirement
/// does not cover. Both requirements are expected in canonical form.
/// </summary>
public ImmutableArray<string> GetUncovered(MergedAuthorization dependency)
{
var uncovered = ImmutableArray.CreateBuilder<string>();

if (dependency.Authenticated && !Authenticated)
{
uncovered.Add(DirectiveNames.Authenticated);
}

if (!AuthorizationGroups.Covers(Scopes, dependency.Scopes))
{
uncovered.Add(DirectiveNames.RequiresScopes);
}

if (!AuthorizationGroups.Covers(Policies, dependency.Policies))
{
uncovered.Add(DirectiveNames.Policy);
}

return uncovered.ToImmutable();
}

private static bool GroupsMatch(
ImmutableArray<ImmutableArray<string>> left,
ImmutableArray<ImmutableArray<string>> right)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public static class LogEntryCodes
public const string AuthorizationGroupCountExceeded = "AUTHORIZATION_GROUP_COUNT_EXCEEDED";
public const string AuthorizationInherited = "AUTHORIZATION_INHERITED";
public const string AuthorizationOnInterfaceObject = "AUTHORIZATION_ON_INTERFACE_OBJECT";
public const string AuthorizationTransitiveRequirementsMissing =
"AUTHORIZATION_TRANSITIVE_REQUIREMENTS_MISSING";
public const string ConflictingApolloFederationVersion =
"CONFLICTING_APOLLO_FEDERATION_VERSION";
public const string ConflictingSourceSchemaName = "CONFLICTING_SOURCE_SCHEMA_NAME";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ public static LogEntry AuthorizationOnInterfaceObject(
.Build();
}

public static LogEntry AuthorizationTransitiveRequirementsMissing(
SchemaCoordinate requiringCoordinate,
SchemaCoordinate dependencyCoordinate,
SchemaCoordinate viaCoordinate,
MutableSchemaDefinition schema,
IEnumerable<string> uncoveredDirectiveNames)
{
return LogEntryBuilder.New()
.SetMessage(
LogEntryHelper_AuthorizationTransitiveRequirementsMissing,
requiringCoordinate,
dependencyCoordinate,
viaCoordinate,
schema.Name,
string.Join(", ", uncoveredDirectiveNames.Select(static n => $"@{n}")))
.SetCode(LogEntryCodes.AuthorizationTransitiveRequirementsMissing)
.SetSeverity(LogSeverity.Error)
.SetCoordinate(requiringCoordinate)
.SetSchema(schema)
.Build();
}

private static string FormatSchemaNames(IEnumerable<string> schemaNames)
=> string.Join(", ", schemaNames.Select(static n => $"'{n}'"));

Expand Down
Loading
Loading