Skip to content

Pass an explicit context to Cms, Common and two-factor services - #937

Merged
KrzysztofPajak merged 9 commits into
developfrom
refactor/business-explicit-context-cms-common
Oct 17, 2026
Merged

KrzysztofPajak merged 9 commits into
developfrom
refactor/business-explicit-context-cms-common

Conversation

@KrzysztofPajak

Copy link
Copy Markdown
Member

Part of the explicit-context work in Business (after #930, #931, #932)
Type: feature

Issue

PageService, NewsService, KnowledgebaseService and TwoFactorAuthenticationService read the current customer and store from IContextAccessor. PermissionService.AuthorizeAction and TranslationService.ImportResourcesFromXml did the same. Outside a request these values are missing or belong to someone else, so results depended on who was calling.

While moving the callers, three bugs turned up:

  1. The page list cache ignored customer groups. PAGES_ALL_KEY was store + ignoreAcl, but the read filters by the current customer's groups. The footer, menu, sitemap and closed-store filter therefore showed every visitor the page list of whoever filled the cache first. A guest could see links to group-limited pages, and a group member could lose them.
  2. The home page news cache had the same problem. HOMEPAGE_NEWSMODEL_KEY was language + store, over a group-filtered news list.
  3. The admin knowledge base filtered articles by the admin's own groups and host store. GetKnowledgebaseArticles(storeId) ignored its storeId and applied the ambient filter. In the admin tree and the related-articles popup, articles limited to a group the admin is not in, or to another store, were hidden, while the categories on the same screen were all listed.

Solution

  • Cms reads take the existing CatalogAccess (as Catalog and checkout attributes already do): For(customer, storeId) limits the result to the customer's groups, and IncludingHidden(storeId) replaces ignoreAcl: true. Web builds it with contextAccessor.CatalogAccess(), or from request.Customer / request.Store where the request carries them. Each caller passes exactly what the service used to read.
  • GenerateCodeSetup takes the Store, whose company name is the authenticator issuer. AuthorizeAction takes the Customer, and PermissionAuthorizeActionAttribute passes the request customer.
  • ImportResourcesFromXml dropped its ambient CreatedBy = CurrentCustomer.Email. The repository overwrites CreatedBy from IAuditInfoProvider on every insert, so the value was never saved, and the read only made the import throw without a current customer.
  • Removed the unused GetTranslationEnum(…, IContextAccessor) overload.
  • ITranslationService.GetResource(string name) and IPermissionService.Authorize(Permission) / Authorize(string) still read the current request: they have about 1,700 call sites. Their docs now say so and point to the explicit overloads.
  • Fixes, each in its own commit with a regression test: the page list key gets the group ids (empty when hidden pages are included), the home page news key gets the group ids, and GetKnowledgebaseArticles() returns every article.

Breaking changes

  • IPageService.GetAllPages(CatalogAccess access) replaces GetAllPages(string storeId, bool ignoreAcl = false).
  • INewsService.GetAllNews(CatalogAccess access, int pageIndex = 0, int pageSize = int.MaxValue, bool includeUnpublished = false, string newsTitle = ""): ignoreAcl → access, showHidden → includeUnpublished.
  • IKnowledgebaseService: GetPublicKnowledgebaseCategory, GetPublicKnowledgebaseCategories, GetPublicKnowledgebaseArticles(), GetPublicKnowledgebaseArticle, GetPublicKnowledgebaseArticlesByCategory, GetPublicKnowledgebaseArticlesByKeyword, GetPublicKnowledgebaseCategoriesByKeyword and GetHomepageKnowledgebaseArticles take a trailing CatalogAccess access. GetKnowledgebaseArticles() lost its unused storeId and no longer filters.
  • ITwoFactorAuthenticationService.GenerateCodeSetup(secretKey, customer, store, language, type).
  • IPermissionService.AuthorizeAction(permissionSystemName, permissionActionName, customer).
  • Removed TranslateExtensions.GetTranslationEnum<T>(T, ITranslationService, IContextAccessor).
  • The constructors of PageService, NewsService, KnowledgebaseService and TwoFactorAuthenticationService no longer take IContextAccessor.
  • Cache keys PAGES_ALL_KEY and HOMEPAGE_NEWSMODEL_KEY have a third segment, the customer group ids. The invalidation prefixes are unchanged.

Testing

Automated: the solution builds, and dotnet test GrandNode.slnx passes (22 projects). New tests cover:

  • the page list per customer groups, including the same groups in another order and IncludingHidden next to For;
  • an empty store id not filtering pages or news;
  • news by groups and includeUnpublished;
  • the home page news handler for two customers with different groups, in both orders;
  • knowledge base reads by groups and with IncludingHidden;
  • the admin article list including limited articles;
  • AuthorizeAction for the given customer;
  • XML import without a current customer.

Manual:

  1. Create a page shown in the footer, limited to a customer group "VIP". Open the store as a VIP customer, then in another browser as a guest: the guest does not see the page in the footer. Repeat in the other order: the VIP customer sees it.
  2. Do the same with a news item limited to "VIP" on the home page.
  3. Admin → Content management → Knowledge base: an article limited to a group the admin is not in, or to another store, appears in the tree and in an article's "Add related article" popup.
  4. Enable two-factor authentication with the app verification type, start the setup as a customer: the authenticator app shows the store's company name as the issuer.
  5. Admin → Customer groups → permissions: deny an action (e.g. Edit on Products) to a staff group. A member of that group cannot use that action, and other actions still work.
  6. Admin → Languages → import an XML resources file: the import succeeds.

Copilot AI balanced review requested due to automatic review settings October 17, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

- Keep the display order of pages read for a store
- Show group-limited pages and news in the admin search
- Skip the knowledge base store filter for an empty store id and keep
  reads including hidden records out of a groupless customer's cache entry
- Leave customer groups out of page, knowledge base and home-page news
  cache keys when the ACL is off
- Guard the store in GenerateCodeSetup and the customer in AuthorizeAction;
  load the customer's groups once there
- Build the catalog access once in the knowledge base keyword search and
  take article parents from the loaded categories
- Flatten the ACL and store filters in GetAllNews and GetAllPages
@KrzysztofPajak
KrzysztofPajak merged commit b44e9d1 into develop Oct 17, 2026
8 of 9 checks passed
@KrzysztofPajak
KrzysztofPajak deleted the refactor/business-explicit-context-cms-common branch October 17, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants