Skip to content

Commit a027afb

Browse files
authored
Add AllowAnonymous extension method and attribute (graphql-dotnet#3079)
1 parent 695e55d commit a027afb

File tree

6 files changed

+129
-21
lines changed

6 files changed

+129
-21
lines changed

docs2/site/docs/migrations/migration5.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -602,12 +602,13 @@ There are a number of other minor issues fixed; see these links for more details
602602
- https://github.com/graphql-dotnet/graphql-dotnet/issues/3002
603603
- https://github.com/graphql-dotnet/graphql-dotnet/pull/3004
604604

605-
### 20. `Authorize` and `AuthorizeWithRoles` extension methods added in GraphQL 5.1.0 and 5.1.1
605+
### 20. `Authorize`, `AuthorizeWithRoles` and `AllowAnonymous` extension methods added in GraphQL 5.1.0 and 5.1.1
606606

607-
This allows for specifying roles rather than just policies that can be used to validate a request.
607+
`AuthorizeWithRoles` allows for specifying roles rather than just policies that can be used to validate a request.
608608
`Authorize` can be used to specify that only authentication is required, without specifying any specific roles or policies.
609-
As with `AuthorizeWithPolicy` (renamed from `AuthorizeWith`), it requires support by a third-party
610-
library to perform the validation.
609+
`AllowAnonymous` typically indicates that anonymous access should be allowed to a field of a graph type requiring authorization,
610+
providing that no other fields were selected. As with `AuthorizeWithPolicy` (renamed from `AuthorizeWith`), these
611+
new methods require support by a third-party library to perform the validation.
611612

612613
Similar to the ASP.NET Core `AuthorizeAttribute`, the new `AuthorizeWithRoles` method accepts
613614
a comma-separated list of role names that would allow access to the graph or field.
@@ -622,6 +623,9 @@ You may also supply a list of strings as in the following example:
622623
graph.AuthorizeWithRoles("Administrators", "Managers");
623624
```
624625

626+
For schema-first and "type-first" graphs, the `[GraphQLAuthorize]` has been updated to support roles and can now
627+
be used without any policy or role names, and an `[AllowAnonymous]` attribute has been added.
628+
625629
### 21. `RequestServices` added to `ValidationContext` in GraphQL 5.1.0
626630

627631
This allows for validation rules to access scoped services if necessary.

src/GraphQL.ApiTests/net6/GraphQL.approved.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
namespace GraphQL
22
{
3+
[System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field)]
4+
public class AllowAnonymousAttribute : GraphQL.GraphQLAttribute
5+
{
6+
public AllowAnonymousAttribute() { }
7+
public override void Modify(GraphQL.Utilities.FieldConfig field) { }
8+
public override void Modify(GraphQL.Types.FieldType fieldType, bool isInputType) { }
9+
}
310
public static class AuthorizationExtensions
411
{
12+
public const string ANONYMOUS_KEY = "Authorization__AllowAnonymous";
513
public const string AUTHORIZE_KEY = "Authorization__Required";
614
public const string POLICY_KEY = "Authorization__Policies";
715
public const string ROLE_KEY = "Authorization__Roles";
16+
public static TMetadataProvider AllowAnonymous<TMetadataProvider>(this TMetadataProvider provider)
17+
where TMetadataProvider : GraphQL.Types.IProvideMetadata { }
818
public static TMetadataProvider Authorize<TMetadataProvider>(this TMetadataProvider provider)
919
where TMetadataProvider : GraphQL.Types.IProvideMetadata { }
1020
[System.Obsolete("Please use AuthorizeWithPolicy instead. Will be removed in v6.")]
@@ -28,6 +38,9 @@ namespace GraphQL
2838
public static GraphQL.Builders.FieldBuilder<TSourceType, TReturnType> AuthorizeWithRoles<TSourceType, TReturnType>(this GraphQL.Builders.FieldBuilder<TSourceType, TReturnType> builder, params string[] roles) { }
2939
public static System.Collections.Generic.List<string>? GetPolicies(this GraphQL.Types.IProvideMetadata provider) { }
3040
public static System.Collections.Generic.List<string>? GetRoles(this GraphQL.Types.IProvideMetadata provider) { }
41+
public static bool IsAnonymousAllowed(this GraphQL.Types.IProvideMetadata provider) { }
42+
public static bool IsAuthorizationRequired(this GraphQL.Types.IProvideMetadata provider) { }
43+
[System.Obsolete("Please use IsAuthorizationRequired. Will be removed in v6.")]
3144
public static bool RequiresAuthorization(this GraphQL.Types.IProvideMetadata provider) { }
3245
}
3346
public class AuthorizeAttribute : GraphQL.GraphQLAttribute

src/GraphQL.ApiTests/netstandard20+netstandard21/GraphQL.approved.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
namespace GraphQL
22
{
3+
[System.AttributeUsage(System.AttributeTargets.Method | System.AttributeTargets.Property | System.AttributeTargets.Field)]
4+
public class AllowAnonymousAttribute : GraphQL.GraphQLAttribute
5+
{
6+
public AllowAnonymousAttribute() { }
7+
public override void Modify(GraphQL.Utilities.FieldConfig field) { }
8+
public override void Modify(GraphQL.Types.FieldType fieldType, bool isInputType) { }
9+
}
310
public static class AuthorizationExtensions
411
{
12+
public const string ANONYMOUS_KEY = "Authorization__AllowAnonymous";
513
public const string AUTHORIZE_KEY = "Authorization__Required";
614
public const string POLICY_KEY = "Authorization__Policies";
715
public const string ROLE_KEY = "Authorization__Roles";
16+
public static TMetadataProvider AllowAnonymous<TMetadataProvider>(this TMetadataProvider provider)
17+
where TMetadataProvider : GraphQL.Types.IProvideMetadata { }
818
public static TMetadataProvider Authorize<TMetadataProvider>(this TMetadataProvider provider)
919
where TMetadataProvider : GraphQL.Types.IProvideMetadata { }
1020
[System.Obsolete("Please use AuthorizeWithPolicy instead. Will be removed in v6.")]
@@ -28,6 +38,9 @@ namespace GraphQL
2838
public static GraphQL.Builders.FieldBuilder<TSourceType, TReturnType> AuthorizeWithRoles<TSourceType, TReturnType>(this GraphQL.Builders.FieldBuilder<TSourceType, TReturnType> builder, params string[] roles) { }
2939
public static System.Collections.Generic.List<string>? GetPolicies(this GraphQL.Types.IProvideMetadata provider) { }
3040
public static System.Collections.Generic.List<string>? GetRoles(this GraphQL.Types.IProvideMetadata provider) { }
41+
public static bool IsAnonymousAllowed(this GraphQL.Types.IProvideMetadata provider) { }
42+
public static bool IsAuthorizationRequired(this GraphQL.Types.IProvideMetadata provider) { }
43+
[System.Obsolete("Please use IsAuthorizationRequired. Will be removed in v6.")]
3144
public static bool RequiresAuthorization(this GraphQL.Types.IProvideMetadata provider) { }
3245
}
3346
public class AuthorizeAttribute : GraphQL.GraphQLAttribute

src/GraphQL.Tests/AuthorizationTests.cs

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ public class AuthorizationTests
88
public void Field()
99
{
1010
var field = new FieldType();
11-
field.RequiresAuthorization().ShouldBeFalse();
11+
field.IsAuthorizationRequired().ShouldBeFalse();
1212
field.AuthorizeWith("Policy1");
13-
field.RequiresAuthorization().ShouldBeTrue();
13+
field.IsAuthorizationRequired().ShouldBeTrue();
1414
field.AuthorizeWith("Policy2");
1515
field.AuthorizeWith("Policy2");
1616
field.AuthorizeWithPolicy("Policy3");
@@ -20,7 +20,7 @@ public void Field()
2020
field.AuthorizeWithRoles("Role1", "Role4");
2121
field.AuthorizeWithRoles("");
2222

23-
field.RequiresAuthorization().ShouldBeTrue();
23+
field.IsAuthorizationRequired().ShouldBeTrue();
2424
field.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
2525
field.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3", "Role4" });
2626
}
@@ -33,16 +33,25 @@ public void NoRoles()
3333
field.AuthorizeWithRoles("");
3434
field.AuthorizeWithRoles(" ");
3535
field.AuthorizeWithRoles(",");
36-
field.RequiresAuthorization().ShouldBeTrue();
36+
field.IsAuthorizationRequired().ShouldBeTrue();
37+
}
38+
39+
[Fact]
40+
public void AllowAnonymous()
41+
{
42+
var field = new FieldType();
43+
field.IsAnonymousAllowed().ShouldBeFalse();
44+
field.AllowAnonymous();
45+
field.IsAnonymousAllowed().ShouldBeTrue();
3746
}
3847

3948
[Fact]
4049
public void Authorize()
4150
{
4251
var field = new FieldType();
43-
field.RequiresAuthorization().ShouldBeFalse();
52+
field.IsAuthorizationRequired().ShouldBeFalse();
4453
field.Authorize();
45-
field.RequiresAuthorization().ShouldBeTrue();
54+
field.IsAuthorizationRequired().ShouldBeTrue();
4655
}
4756

4857
[Fact]
@@ -60,7 +69,7 @@ public void FieldBuilder()
6069
.AuthorizeWithRoles("Role1", "Role4");
6170

6271
var field = graph.Fields.Find("Field");
63-
field.RequiresAuthorization().ShouldBeTrue();
72+
field.IsAuthorizationRequired().ShouldBeTrue();
6473
field.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
6574
field.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3", "Role4" });
6675
}
@@ -81,7 +90,7 @@ public void ConnectionBuilder()
8190
.AuthorizeWithRoles("Role1", "Role4");
8291

8392
var field = graph.Fields.Find("Field");
84-
field.RequiresAuthorization().ShouldBeTrue();
93+
field.IsAuthorizationRequired().ShouldBeTrue();
8594
field.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
8695
field.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3", "Role4" });
8796
}
@@ -90,19 +99,26 @@ public void ConnectionBuilder()
9099
public void AutoOutputGraphType()
91100
{
92101
var graph = new AutoRegisteringObjectGraphType<Class1>();
93-
graph.RequiresAuthorization().ShouldBeTrue();
102+
graph.IsAuthorizationRequired().ShouldBeTrue();
103+
graph.IsAnonymousAllowed().ShouldBeFalse();
94104
graph.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
95105
graph.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3" });
96106

97-
graph.Fields.Find("Id").RequiresAuthorization().ShouldBeFalse();
107+
graph.Fields.Find("Id").IsAuthorizationRequired().ShouldBeFalse();
98108

99109
var field = graph.Fields.Find("Name");
100-
field.RequiresAuthorization().ShouldBeTrue();
110+
field.IsAuthorizationRequired().ShouldBeTrue();
111+
field.IsAnonymousAllowed().ShouldBeFalse();
101112
field.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
102113
field.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3" });
103114

104115
field = graph.Fields.Find("Value");
105-
field.RequiresAuthorization().ShouldBeTrue();
116+
field.IsAuthorizationRequired().ShouldBeTrue();
117+
field.IsAnonymousAllowed().ShouldBeFalse();
118+
119+
field = graph.Fields.Find("Public");
120+
field.IsAuthorizationRequired().ShouldBeFalse();
121+
field.IsAnonymousAllowed().ShouldBeTrue();
106122
}
107123

108124
[Fact]
@@ -114,23 +130,31 @@ type Class1 {
114130
id: String!
115131
name: String!
116132
value: String!
133+
public: String!
117134
}",
118135
configure => configure.Types.Include<Class1>());
119136

120137
var graph = (ObjectGraphType)schema.AllTypes["Class1"];
121-
graph.RequiresAuthorization().ShouldBeTrue();
138+
graph.IsAuthorizationRequired().ShouldBeTrue();
139+
graph.IsAnonymousAllowed().ShouldBeFalse();
122140
graph.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
123141
graph.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3" });
124142

125-
graph.Fields.Find("id").RequiresAuthorization().ShouldBeFalse();
143+
graph.Fields.Find("id").IsAuthorizationRequired().ShouldBeFalse();
126144

127145
var field = graph.Fields.Find("name");
128-
field.RequiresAuthorization().ShouldBeTrue();
146+
field.IsAuthorizationRequired().ShouldBeTrue();
147+
field.IsAnonymousAllowed().ShouldBeFalse();
129148
field.GetPolicies().ShouldBe(new string[] { "Policy1", "Policy2", "Policy3" });
130149
field.GetRoles().ShouldBe(new string[] { "Role1", "Role2", "Role3" });
131150

132151
field = graph.Fields.Find("value");
133-
field.RequiresAuthorization().ShouldBeTrue();
152+
field.IsAuthorizationRequired().ShouldBeTrue();
153+
field.IsAnonymousAllowed().ShouldBeFalse();
154+
155+
field = graph.Fields.Find("public");
156+
field.IsAuthorizationRequired().ShouldBeFalse();
157+
field.IsAnonymousAllowed().ShouldBeTrue();
134158
}
135159

136160
[Authorize("Policy1")]
@@ -151,5 +175,7 @@ private class Class1
151175
public string Name { get; set; }
152176
[Authorize]
153177
public string Value { get; set; }
178+
[AllowAnonymous]
179+
public string Public { get; set; }
154180
}
155181
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using GraphQL.Types;
2+
using GraphQL.Utilities;
3+
4+
namespace GraphQL;
5+
6+
/// <summary>
7+
/// Attribute to typically indicate that anonymous access should be allowed to a field of a graph type
8+
/// requiring authorization, providing that no other fields were selected.
9+
/// </summary>
10+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method | AttributeTargets.Field)]
11+
public class AllowAnonymousAttribute : GraphQLAttribute
12+
{
13+
/// <inheritdoc />
14+
public override void Modify(FieldConfig field)
15+
{
16+
field.AllowAnonymous();
17+
}
18+
19+
/// <inheritdoc />
20+
public override void Modify(FieldType fieldType, bool isInputType)
21+
{
22+
fieldType.AllowAnonymous();
23+
}
24+
}

src/GraphQL/Authorization/AuthorizationExtensions.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public static class AuthorizationExtensions
2626
/// </summary>
2727
public const string AUTHORIZE_KEY = "Authorization__Required";
2828

29+
/// <summary>
30+
/// Metadata key name for typically indicating if anonymous access should be allowed to a field of a graph type
31+
/// requiring authorization, providing that no other fields were selected.
32+
/// </summary>
33+
public const string ANONYMOUS_KEY = "Authorization__AllowAnonymous";
34+
2935
/// <summary>
3036
/// Gets a list of authorization policy names for the specified metadata provider if any.
3137
/// Otherwise returns <see langword="null"/>.
@@ -48,6 +54,23 @@ public static class AuthorizationExtensions
4854
/// <returns> List of authorization role names applied to this metadata provider. </returns>
4955
public static List<string>? GetRoles(this IProvideMetadata provider) => provider.GetMetadata<List<string>>(ROLE_KEY);
5056

57+
/// <summary>
58+
/// Returns a boolean typically indicating if anonymous access should be allowed to a field of a graph type
59+
/// requiring authorization, providing that no other fields were selected.
60+
/// </summary>
61+
public static bool IsAnonymousAllowed(this IProvideMetadata provider) => provider.GetMetadata(ANONYMOUS_KEY, false);
62+
63+
/// <summary>
64+
/// Adds metadata to typically indicate that anonymous access should be allowed to a field of a graph type
65+
/// requiring authorization, providing that no other fields were selected.
66+
/// </summary>
67+
public static TMetadataProvider AllowAnonymous<TMetadataProvider>(this TMetadataProvider provider)
68+
where TMetadataProvider : IProvideMetadata
69+
{
70+
provider.Metadata[ANONYMOUS_KEY] = true;
71+
return provider;
72+
}
73+
5174
/// <summary>
5275
/// Gets a boolean value that determines whether any authorization policy is applied to this metadata provider.
5376
/// </summary>
@@ -56,9 +79,14 @@ public static class AuthorizationExtensions
5679
/// <see cref="FieldType"/>, <see cref="Schema"/> or others.
5780
/// </param>
5881
/// <returns> <c>true</c> if any authorization policy is applied, otherwise <c>false</c>. </returns>
59-
public static bool RequiresAuthorization(this IProvideMetadata provider)
82+
public static bool IsAuthorizationRequired(this IProvideMetadata provider)
6083
=> provider.GetMetadata(AUTHORIZE_KEY, false) || GetPolicies(provider)?.Count > 0 || GetRoles(provider)?.Count > 0;
6184

85+
/// <inheritdoc cref="IsAuthorizationRequired(IProvideMetadata)"/>
86+
[Obsolete("Please use IsAuthorizationRequired. Will be removed in v6.")]
87+
public static bool RequiresAuthorization(this IProvideMetadata provider)
88+
=> provider.IsAuthorizationRequired();
89+
6290
/// <summary>
6391
/// Adds metadata to indicate that the resource requires that the user has successfully authenticated.
6492
/// </summary>

0 commit comments

Comments
 (0)