-
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
207 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
using GraphQL.Types; | ||
using Xunit; | ||
|
||
namespace GraphQL.Authorization.Tests | ||
{ | ||
/// <summary> | ||
/// Tests for <see cref="IntrospectionSkipCondition"/>. | ||
/// https://github.com/graphql-dotnet/authorization/issues/28 | ||
/// </summary> | ||
public class AuthorizationSkipTests : ValidationTestBase | ||
{ | ||
[Fact] | ||
public void passes_with_skip_condition() | ||
{ | ||
Rule = new AuthorizationValidationRule(new AuthorizationEvaluator(Settings), new[] { new IntrospectionSkipCondition() }); | ||
Settings.AddPolicy("AdminPolicy", _ => _.RequireClaim("admin")); | ||
|
||
ShouldPassRule(config => | ||
{ | ||
config.Query = QUERY; | ||
config.Schema = CreateSchema(); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void fails_without_skip_condition() | ||
{ | ||
Settings.AddPolicy("AdminPolicy", _ => _.RequireClaim("admin")); | ||
|
||
ShouldFailRule(config => | ||
{ | ||
config.Query = QUERY; | ||
config.Schema = CreateSchema(); | ||
}); | ||
} | ||
|
||
[Fact] | ||
public void fails_with_skip_condition_and_extra_fields() | ||
{ | ||
Rule = new AuthorizationValidationRule(new AuthorizationEvaluator(Settings), new[] { new IntrospectionSkipCondition() }); | ||
Settings.AddPolicy("AdminPolicy", _ => _.RequireClaim("admin")); | ||
|
||
ShouldFailRule(config => | ||
{ | ||
config.Query = QUERY.Replace("...frag1", "...frag1 info"); | ||
config.Schema = CreateSchema(); | ||
}); | ||
} | ||
|
||
private static ISchema CreateSchema() => | ||
Schema.For("type Query { info: String! }", builder => builder.Types.Include<Query>()); | ||
|
||
[GraphQLAuthorize("AdminPolicy")] | ||
public class Query | ||
{ | ||
public string Info() => "OK"; | ||
} | ||
|
||
private const string QUERY = @" | ||
query | ||
{ | ||
__typename | ||
__type(name: ""__Schema"") | ||
{ | ||
name | ||
description | ||
} | ||
x: __schema | ||
{ | ||
queryType | ||
{ | ||
name | ||
} | ||
} | ||
...frag1 | ||
... on Query | ||
{ | ||
inline: __typename | ||
} | ||
} | ||
fragment frag1 on Query | ||
{ | ||
s: __typename | ||
}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using System.Threading.Tasks; | ||
using GraphQL.Validation; | ||
|
||
namespace GraphQL.Authorization | ||
{ | ||
/// <summary> | ||
/// Allows to conditionally skip entire AST traversing and all | ||
/// authorization checks in <see cref="AuthorizationValidationRule"/>. | ||
/// </summary> | ||
public interface IAuthorizationSkipCondition | ||
{ | ||
/// <summary> | ||
/// Specifies whether authorization checks should be skipped. | ||
/// </summary> | ||
ValueTask<bool> ShouldSkip(ValidationContext context); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using GraphQL.Language.AST; | ||
using GraphQL.Validation; | ||
|
||
namespace GraphQL.Authorization | ||
{ | ||
/// <summary> | ||
/// Skips authorization checks for introspection queries, namely all queries | ||
/// that contain only __schema, __type and __typename top-level fields. | ||
/// </summary> | ||
public class IntrospectionSkipCondition : IAuthorizationSkipCondition | ||
{ | ||
/// <inheritdoc /> | ||
public ValueTask<bool> ShouldSkip(ValidationContext context) | ||
{ | ||
static bool IsIntrospectionField(Field f) => f.Name == "__schema" || f.Name == "__type" || f.Name == "__typename"; | ||
|
||
bool ContainsOnlyIntrospectionFields(IHaveSelectionSet node) | ||
{ | ||
if (node.SelectionSet?.Selections?.Count == 0) | ||
return false; // invalid document, better to return false | ||
|
||
foreach (var selection in node.SelectionSet!.Selections) | ||
{ | ||
switch (selection) | ||
{ | ||
case Field field: | ||
if (!IsIntrospectionField(field)) | ||
return false; | ||
break; | ||
|
||
case InlineFragment inlineFragment: | ||
if (!ContainsOnlyIntrospectionFields(inlineFragment)) | ||
return false; | ||
break; | ||
|
||
case FragmentSpread fragmentSpread: | ||
var fragmentDef = context.Document.Fragments.FindDefinition(fragmentSpread.Name); | ||
if (fragmentDef == null || !ContainsOnlyIntrospectionFields(fragmentDef)) | ||
return false; | ||
break; | ||
|
||
default: | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
var actualOperation = context.Document.Operations.FirstOrDefault(x => x.Name == context.OperationName) ?? context.Document.Operations.FirstOrDefault(); | ||
|
||
return new ValueTask<bool>(actualOperation?.OperationType == OperationType.Query | ||
? ContainsOnlyIntrospectionFields(actualOperation) | ||
: false); // not an executable document | ||
} | ||
} | ||
} |