Make the store scope explicit on the category, brand and collection lists - #767
Merged
Conversation
…d collection lists GetAllCategories, GetAllBrands and GetAllCollections all defaulted storeId to "", and AclService.Authorize returns true for an empty store. Omitting the argument was therefore not a compile error and silently turned tenant filtering off - which is exactly what had happened in the storefront search page: it listed collections from every store, while the category block ten lines above it passed request.Store.Id. storeId cannot simply move to the front of the parameter list. All three methods take string parameters before it, so reordering would still compile at positional call sites while quietly swapping the arguments' meaning - the very class of bug being fixed here. Instead every parameter up to and including storeId is now required, with the order untouched. The compiler then named all 27 call sites and no call could change meaning without failing to build. Twenty-six of them already passed the right scope and only became explicit. The twenty-seventh, GetSearchHandler, is the defect above and now passes the request's store. AclServiceTest and the new AclMappingExtensionTests pin the fail-open, so a future change to it has to be deliberate. The latter also records a difference nobody had written down: AclService.Authorize admits an entity shared with a second store, while AccessToEntityByStore refuses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Type: bugfix
Issue
GetAllCategories,GetAllBrandsandGetAllCollectionsall declaredstring storeId = "", andAclService.Authorizereturnstruewhen the store is empty:So omitting the argument was not a compile error and silently turned tenant filtering off. That is not hypothetical — it had already happened:
The storefront search page listed collections from every store.
GetSearchHandlerbuilds the search filter dropdowns; the category block passesrequest.Store.Id, and ten lines below it the collection block did not:To reproduce: run two stores, give store B a collection limited to B, and open the search page of store A — B's collection appears in the "collection" filter.
Solution
storeIdcannot simply move to the front of the parameter list. All three methods takestringparameters before it (parentId,categoryName,brandName,collectionName), so reordering would still compile at positional call sites while quietly swapping the arguments' meaning — the very class of bug this PR is about.Instead, every parameter up to and including
storeIdbecame required, with the order untouched. The compiler then named all 27 call sites, and no call could change meaning without failing to build.Twenty-six of those already passed the right scope and only became explicit — admin panel calls that are deliberately global now say
storeId: ""in as many words. The twenty-seventh is the defect above and now passes the request's store.AclServiceTestand the newAclMappingExtensionTestspin the fail-open, so changing it later has to be deliberate. The latter also records a difference nobody had written down:AclService.Authorizeadmits an entity shared with a second store, whileAclMappingExtension.AccessToEntityByStorerefuses it (Stores.Count == 1).This is the first instalment of item 2.3 in the architecture audit. Roughly 150 further
string storeId = ""declarations remain in the business layer, the CMS services (BlogService,NewsService,PageService) being the natural next batch.Breaking changes
Yes, for third-party plugins.
ICategoryService.GetAllCategories,IBrandService.GetAllBrandsandICollectionService.GetAllCollectionsno longer default their leading parameters. Plugin code calling them without those arguments will not compile until it passes them; parameter order and types are unchanged, so no call can silently change meaning. This is the point of the change — the scope has to be a decision, not an omission.Storefront behaviour changes in exactly one place: the search page's collection filter now honours the current store, as the rest of the page already did.
Testing
dotnet build ./GrandNode.sln— clean, 0 warnings.dotnet teston the affected projects:Grand.Business.Catalog.Tests(349),Grand.Business.Common.Tests(127),Grand.Web.Admin.Tests(55),Grand.Web.Store.Tests(17),Grand.Web.Tests(9),Grand.Business.Messages.Tests(33) — all pass./search) and expand the collection filter: store B's collection must not appear. Before this change it did.🤖 Generated with Claude Code