From b333da9763b7b2c09362a75295256442ee95d83f Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Wed, 1 Apr 2026 11:43:33 +0200 Subject: [PATCH 1/3] Improve GraphQL content item authorization handling Refactored ContentItemQuery to remove IHttpContextAccessor and use context.RequestServices for service resolution. Added user authorization checks before returning content items in GraphQL queries, ensuring only authorized users can access content. Passed context.User into GraphQL execution options for resolver access. Cleaned up constructor and removed unnecessary usings. Enhances security and modernizes dependency injection usage. --- .../GraphQLMiddleware.cs | 1 + .../Queries/ContentItemQuery.cs | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs index a7a2a14dcf7..257f1e2d350 100644 --- a/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs +++ b/src/OrchardCore.Modules/OrchardCore.Apis.GraphQL/GraphQLMiddleware.cs @@ -158,6 +158,7 @@ private async Task ExecuteAsync(HttpContext context) options.OperationName = request.OperationName; options.Variables = request.Variables; options.UserContext = _settings.BuildUserContext?.Invoke(context); + options.User = context.User; options.ValidationRules = DocumentValidator.CoreRules .Concat(context.RequestServices.GetServices()) .Append(new ComplexityValidationRule(new ComplexityOptions diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs index 0daf3e02aab..1b276e907dc 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs @@ -1,7 +1,7 @@ using GraphQL; using GraphQL.Resolvers; using GraphQL.Types; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using OrchardCore.Apis.GraphQL; @@ -11,14 +11,10 @@ namespace OrchardCore.ContentManagement.GraphQL.Queries; public sealed class ContentItemQuery : ISchemaBuilder { - private readonly IHttpContextAccessor _httpContextAccessor; - internal readonly IStringLocalizer S; - public ContentItemQuery(IHttpContextAccessor httpContextAccessor, - IStringLocalizer localizer) + public ContentItemQuery(IStringLocalizer localizer) { - _httpContextAccessor = httpContextAccessor; S = localizer; } @@ -50,8 +46,17 @@ public Task BuildAsync(ISchema schema) private async ValueTask ResolveAsync(IResolveFieldContext context) { var contentItemId = context.GetArgument("contentItemId"); - var contentManager = _httpContextAccessor.HttpContext.RequestServices.GetService(); + var contentManager = context.RequestServices.GetService(); + var authorizationService = context.RequestServices.GetService(); + + var contentItem = await contentManager.GetAsync(contentItemId); + + if (!await authorizationService.AuthorizeAsync(context.User, Contents.CommonPermissions.ViewContent, contentItem)) + { + // Return null if the user doesn't have permission to view the content item, so that it doesn't appear in the GraphQL response. + return null; + } - return await contentManager.GetAsync(contentItemId); + return contentItem; } } From 9ae5ecf5588cda71a2fc1d43d6c05a49c562f602 Mon Sep 17 00:00:00 2001 From: Georg von Kries Date: Thu, 2 Apr 2026 10:26:13 +0200 Subject: [PATCH 2/3] Require ExecuteGraphQL for contentItem query; add tests Enforce ExecuteGraphQL permission on the contentItem GraphQL field and ensure null is returned if the content item does not exist. Add tests to verify access control for users with and without ViewContent permission. --- .../Queries/ContentItemQuery.cs | 7 ++ .../ContentItemQueryTests.cs | 77 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs diff --git a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs index 1b276e907dc..a654e47f018 100644 --- a/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs +++ b/src/OrchardCore/OrchardCore.ContentManagement.GraphQL/Queries/ContentItemQuery.cs @@ -38,6 +38,8 @@ public Task BuildAsync(ISchema schema) Resolver = new FuncFieldResolver(ResolveAsync), }; + field.RequirePermission(GraphQLPermissions.ExecuteGraphQL); + schema.Query.AddField(field); return Task.CompletedTask; @@ -51,6 +53,11 @@ private async ValueTask ResolveAsync(IResolveFieldContext context) var contentItem = await contentManager.GetAsync(contentItemId); + if (contentItem == null) + { + return null; + } + if (!await authorizationService.AuthorizeAsync(context.User, Contents.CommonPermissions.ViewContent, contentItem)) { // Return null if the user doesn't have permission to view the content item, so that it doesn't appear in the GraphQL response. diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs new file mode 100644 index 00000000000..6e1b23fdca4 --- /dev/null +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs @@ -0,0 +1,77 @@ +using OrchardCore.Apis.GraphQL; +using OrchardCore.ContentManagement; +using OrchardCore.ContentManagement.Records; +using OrchardCore.Tests.Apis.Context; + +namespace OrchardCore.Tests.Apis.GraphQL; + +public class ContentItemQueryTests +{ + [Fact] + public async Task ShouldReturnContentItemWhenViewContentPermissionIsGranted() + { + using var context = new SiteContext() + .WithRecipe("Blog") + .WithPermissionsContext(new PermissionsContext + { + UsePermissionsContext = true, + AuthorizedPermissions = + [ + GraphQLPermissions.ExecuteGraphQL, + global::OrchardCore.Contents.CommonPermissions.ViewContent, + ], + }); + + await context.InitializeAsync(); + + var contentItemId = await GetPublishedContentItemIdAsync(context, "Blog"); + + var result = await context.GraphQLClient.Content + .Query($@"item: contentItem(contentItemId: ""{contentItemId}"") {{ + contentItemId + }}"); + + Assert.Equal(contentItemId, result["data"]["item"]["contentItemId"].ToString()); + } + + [Fact] + public async Task ShouldNotReturnContentItemWithoutViewContentPermission() + { + using var context = new SiteContext() + .WithRecipe("Blog") + .WithPermissionsContext(new PermissionsContext + { + UsePermissionsContext = true, + AuthorizedPermissions = + [ + GraphQLPermissions.ExecuteGraphQL, + ], + }); + + await context.InitializeAsync(); + + var contentItemId = await GetPublishedContentItemIdAsync(context, "Blog"); + + var result = await context.GraphQLClient.Content + .Query($@"item: contentItem(contentItemId: ""{contentItemId}"") {{ + contentItemId + }}"); + + Assert.Null(result["data"]["item"]); + } + + private static async Task GetPublishedContentItemIdAsync(SiteContext context, string contentType) + { + string contentItemId = null; + + await context.UsingTenantScopeAsync(async scope => + { + var session = scope.ServiceProvider.GetRequiredService(); + var contentItem = await session.Query(x => x.ContentType == contentType && x.Published).FirstOrDefaultAsync(); + + contentItemId = contentItem.ContentItemId; + }); + + return contentItemId; + } +} From 058ec65cb3666ee6d05d26493153a3a2bf597f66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 24 Apr 2026 08:18:03 +0000 Subject: [PATCH 3/3] Fix ambiguous ISession reference in ContentItemQueryTests Agent-Logs-Url: https://github.com/OrchardCMS/OrchardCore/sessions/b57c85f7-d211-44c9-97a2-efa977a88e67 Co-authored-by: gvkries <7062785+gvkries@users.noreply.github.com> --- .../Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs index 297c3e3e3ff..8a27298e48c 100644 --- a/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs +++ b/test/OrchardCore.Tests/Apis/GraphQL/ContentManagement/ContentItemQueryTests.cs @@ -68,7 +68,7 @@ private static async Task GetPublishedContentItemIdAsync(SiteContext con await context.UsingTenantScopeAsync(async scope => { - var session = scope.ServiceProvider.GetRequiredService(); + var session = scope.ServiceProvider.GetRequiredService(); var contentItem = await session.Query(x => x.ContentType == contentType && x.Published).FirstOrDefaultAsync(); contentItemId = contentItem.ContentItemId;