Repository navigation
Move the request-customer Authorize overloads to Web - #941
Merged
Merged
Conversation
IPermissionService (Business) now only authorizes a customer the caller passes in, and PermissionService no longer takes IContextAccessor. The new IRequestPermissionService : IPermissionService in Grand.Web.Common.Security.Authorization adds Authorize(Permission) and Authorize(string) for the customer of the current request. Web, the admin panels, Razor views, filters and plugins inject it; the calls are unchanged. Grand.Module.Api has no Web.Common reference, so its controllers and AuthorizeApiAdminAttribute pass WorkContext.CurrentCustomer explicitly. Authorize(string, Customer) returns false for a null customer instead of throwing, matching Authorize(Permission, Customer). Breaking for external plugins that call the single-argument overloads through IPermissionService.
added 2 commits
October 18, 2026 09:14
IRequestPermissionService no longer extends IPermissionService, so request-side callers cannot create, update or delete permissions. RequestPermissionService delegates to the registered IPermissionService instead of inheriting PermissionService, so a replaced or decorated permission service also applies to Web filters, views and components. PermissionAuthorizeAction and CustomerGroupAclUpdateValidator take IPermissionService for AuthorizeAction and GetPermissionById, and a missing customer now denies access instead of throwing. API controllers read the caller through BaseApiController.CurrentCustomer rather than injecting IContextAccessor each, and the admin API filter tolerates a missing work context.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of the explicit-context work in Business (after #930–#940)
Type: feature
Issue
IPermissionService.Authorize(Permission)andAuthorize(string)authorize the customer of the current request. They sat on the Business interface, andPermissionServiceinjectedIContextAccessoronly to serve them, so nothing stopped Business code from calling them. Outside a request (scheduled task, background handler) such a check uses a null or wrong customer. #937 documented these overloads as the last accepted remainder of M1; #940 moved the matchingGetResource(name)overload to Web.Solution
IPermissionService(Grand.Business.Core) keeps only the overloads that take aCustomer.PermissionServiceno longer takesIContextAccessor, sosrc/Businesshas no ambient read of the request left (the M1 grepIContextAccessor|IWorkContextoversrc/Businessreturns nothing).IRequestPermissionServiceinGrand.Web.Common.Security.Authorizationwith onlyAuthorize(Permission)andAuthorize(string).RequestPermissionServicewrapsIPermissionServiceand passesWorkContext?.CurrentCustomer; a request without a work context is not authorized.AuthorizeAdmin/Store/Vendor/AdminOrStore,PublicStore,ClosedStore,PermissionAuthorize),SiteMap, the admin panels (Admin, Store, Vendor, AdminShared), Grand.Web, Razor views and plugins injectIRequestPermissionServicefor request-customer checks. Code that already passes a customer, or reads/manages permissions, keepsIPermissionService(a class needing both injects both, e.g.CustomerGroupAclUpdateValidator).PermissionAuthorizeActionresolves the customer once and usesIPermissionServicefor both checks.Grand.Module.Apidoes not referenceGrand.Web.Common.BaseApiControllerexposesCurrentCustomer(work-context customer resolved fromHttpContext.RequestServices), and API controllers pass it toIPermissionService.Authorize(..., CurrentCustomer); their constructors are unchanged.AuthorizeApiAdminAttributetakesIContextAccessor.Authorize(string, Customer)returnsfalsefor a null customer instead of throwing, asAuthorize(Permission, Customer)already did..ai/guides describe the new contract.Breaking changes
IPermissionService.Authorize(Permission)andAuthorize(string)are removed. Plugins call them onIRequestPermissionService(Grand.Web.Common.Security.Authorization) or pass a customer. A plugin built against the old interface fails withMissingMethodExceptionuntil rebuilt.PermissionServiceconstructor:(IRepository<Permission>, IRepository<PermissionAction>, IGroupService, ICacheBase);IContextAccessorremoved.IRequestPermissionServiceinstead ofIPermissionService.Testing
Automated: the solution builds, and
dotnet test GrandNode.slnxpasses.Grand.Business.Customers.TestsandGrand.Business.Marketing.Testsfailed in the parallel full run and pass on their own (known parallel flake). NewRequestPermissionServiceTestscover the request customer for both overloads and a request without a work context;PermissionServiceTestsuse an explicit customer and cover a null customer;AuthorizeApiAdminAttributeTestscheck the API filter authorizes the work-context customer.ContentPreviewTestsswap the mock toIRequestPermissionServiceand also complete the controller setup (IPageHeadBuilder,Url,TempData) the blog preview path needs.Manual (not run yet):
/api/...with a token) as an admin and as a customer without the API/admin permission: 200 vs 403.