From 557614bfc39e3a8948898e0a0266c145c6486cbf Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Wed, 11 Jun 2025 22:56:08 -0400 Subject: [PATCH 1/4] Support [AllowAnonymous] and [Authorize] simultaneously on a field --- .../AuthorizationVisitorBase.cs | 4 +-- src/Tests/AuthorizationTests.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs index 63c61d8..7a70e4f 100644 --- a/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs +++ b/src/GraphQL.AspNetCore3/AuthorizationVisitorBase.cs @@ -57,9 +57,7 @@ public virtual async ValueTask EnterAsync(ASTNode node, ValidationContext contex _onlyAnonymousSelected.Push(ti); // Fields, unlike types, are validated immediately. - if (!fieldAnonymousAllowed) { - await ValidateAsync(field, node, context); - } + await ValidateAsync(field, node, context); } // prep for descendants, if any diff --git a/src/Tests/AuthorizationTests.cs b/src/Tests/AuthorizationTests.cs index 068e265..96f3f05 100644 --- a/src/Tests/AuthorizationTests.cs +++ b/src/Tests/AuthorizationTests.cs @@ -751,6 +751,37 @@ public async Task EndToEnd(bool authenticated) actual.ShouldBe(@"{""errors"":[{""message"":""Access denied for field \u0027parent\u0027 on type \u0027QueryType\u0027."",""locations"":[{""line"":1,""column"":3}],""extensions"":{""code"":""ACCESS_DENIED"",""codes"":[""ACCESS_DENIED""]}}]}"); } + [Theory] + [InlineData("Role1", false, false)] // User with Role1, child requires Role2 - should fail at child level + [InlineData("Role2", false, false)] // User with Role2, query requires Role1 - should fail at query level + [InlineData("Role1,Role2", false, true)] // User with both roles - should pass + [InlineData(null, false, false)] // Unauthenticated user - should fail at query level + [InlineData("Role1", true, false)] // User with Role1, child requires Role2 and is anonymous - should fail + [InlineData("Role2", true, true)] // User with Role2, child requires Role2 and is anonymous - should pass + [InlineData("Role1,Role2", true, true)] // User with both roles, child is anonymous - should pass + [InlineData(null, true, false)] // Unauthenticated user, child is anonymous - should fail due as Role2 missing + public void BothAnonymousAndRequirements(string? userRoles, bool childIsAnonymous, bool expectedIsValid) + { + // Set up query to require Role1 + _query.AuthorizeWithRoles("Role1"); + + // Set up child field to require Role2 and optionally be anonymous + _field.AuthorizeWithRoles("Role2"); + if (childIsAnonymous) + _field.AllowAnonymous(); + + // Set up user principal based on test parameters + if (userRoles != null) + { + var roles = userRoles.Split(','); + var claims = roles.Select(role => new Claim(ClaimTypes.Role, role)).ToArray(); + _principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookie")); + } + + var ret = Validate(@"{ parent { child } }"); + ret.IsValid.ShouldBe(expectedIsValid); + } + public enum Mode { None, From 69800511a6a76bdefb2365a1dde960de375e9912 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Wed, 11 Jun 2025 23:05:56 -0400 Subject: [PATCH 2/4] update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e4a2e70..c143bce 100644 --- a/README.md +++ b/README.md @@ -320,8 +320,8 @@ Both roles and policies are supported for output graph types, fields on output g and query arguments. If multiple policies are specified, all must match; if multiple roles are specified, any one role must match. You may also use `.Authorize()` or the `[Authorize]` attribute to validate that the user has authenticated. You may also use -`.AllowAnonymous()` and `[AllowAnonymous]` to allow fields to be returned to -unauthenticated users within an graph that has an authorization requirement defined. +`.AllowAnonymous()` and/or `[AllowAnonymous]` to allow fields to bypass authorization +requirements defined on the type that contains the field. Please note that authorization rules do not apply to values returned within introspection requests, potentially leaking information about protected areas of the schema to unauthenticated users. From 475c1506c30893b869666b09f62ef2cd1c5d8c4b Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Wed, 11 Jun 2025 23:18:14 -0400 Subject: [PATCH 3/4] fix formatting --- src/Tests/AuthorizationTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Tests/AuthorizationTests.cs b/src/Tests/AuthorizationTests.cs index 96f3f05..6111306 100644 --- a/src/Tests/AuthorizationTests.cs +++ b/src/Tests/AuthorizationTests.cs @@ -764,15 +764,14 @@ public void BothAnonymousAndRequirements(string? userRoles, bool childIsAnonymou { // Set up query to require Role1 _query.AuthorizeWithRoles("Role1"); - + // Set up child field to require Role2 and optionally be anonymous _field.AuthorizeWithRoles("Role2"); if (childIsAnonymous) _field.AllowAnonymous(); // Set up user principal based on test parameters - if (userRoles != null) - { + if (userRoles != null) { var roles = userRoles.Split(','); var claims = roles.Select(role => new Claim(ClaimTypes.Role, role)).ToArray(); _principal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookie")); From b5f2ce452712d52bbb427a72c0f420ea7f099639 Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Wed, 11 Jun 2025 23:20:34 -0400 Subject: [PATCH 4/4] update --- src/Tests/AuthorizationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/AuthorizationTests.cs b/src/Tests/AuthorizationTests.cs index 6111306..99e286b 100644 --- a/src/Tests/AuthorizationTests.cs +++ b/src/Tests/AuthorizationTests.cs @@ -759,7 +759,7 @@ public async Task EndToEnd(bool authenticated) [InlineData("Role1", true, false)] // User with Role1, child requires Role2 and is anonymous - should fail [InlineData("Role2", true, true)] // User with Role2, child requires Role2 and is anonymous - should pass [InlineData("Role1,Role2", true, true)] // User with both roles, child is anonymous - should pass - [InlineData(null, true, false)] // Unauthenticated user, child is anonymous - should fail due as Role2 missing + [InlineData(null, true, false)] // Unauthenticated user, child is anonymous - should fail as Role2 is missing public void BothAnonymousAndRequirements(string? userRoles, bool childIsAnonymous, bool expectedIsValid) { // Set up query to require Role1