From e6ae300e0c63d268e32fb51dcde74d74c57d60e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 2 Jul 2026 13:39:22 +0000 Subject: [PATCH] Group-scoped page/paragraph gates need the deny+grant pair: correct the role-string-only rule --- .claude-plugin/marketplace.json | 2 +- CHANGELOG.md | 17 +++++++++++ .../foundational/users-permissions.md | 30 +++++++++++-------- .../references/dw10-canonical-surfaces.md | 2 +- skills/dw-users-permissions/SKILL.md | 7 +++-- 5 files changed, 41 insertions(+), 17 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index dbdc2aa..f508c36 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "Claude skills for Dynamicweb 10 — organized by task domain, bundled by role.", - "version": "3.8.1" + "version": "3.8.2" }, "plugins": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index c359784..cd3c10e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to the Dynamicweb Skills plugin are recorded here. The `version` field in `.claude-plugin/marketplace.json` tracks these entries. +## [3.8.2] + +### Changed +- **Group-scoped page/paragraph gates work — via the deny+grant pair; 3.8.1's role-string-only rule + was a misdiagnosis.** Further live verification on the same 10.26.x host: a **bare** group-id + grant does not gate because highest-wins resolution lets the inherited broad + `AuthenticatedFrontend` grant override it — which is what previously read as "group gating is + non-functional". The working shape (verified at page AND paragraph level): an explicit + `AuthenticatedFrontend → None` deny **plus** a ` → Read` grant on the same entity. + Also live-verifies the `PermissionName='Paragraph'` row shape (previously hedged), with the + deny+grant pair written via the paragraph's Permissions panel or direct SQL + security-cache + flush. Rewrote the §15 role-string rule in + `dw-demo-base/references/foundational/users-permissions.md` around the pair (including the + two-step hide-a-subtree recipe, whose step-2 group grants are effective exactly because of the + step-1 deny), the restrict-a-page recipe in `dw-users-permissions/SKILL.md` (the broad-role deny + named as load-bearing), and the `dw10-canonical-surfaces.md` routing row. + ## [3.8.1] ### Changed diff --git a/skills/dw-demo-base/references/foundational/users-permissions.md b/skills/dw-demo-base/references/foundational/users-permissions.md index 9124cdc..df01bce 100644 --- a/skills/dw-demo-base/references/foundational/users-permissions.md +++ b/skills/dw-demo-base/references/foundational/users-permissions.md @@ -636,12 +636,15 @@ rows on that build, and `AccessElementPermission` (which also exists) stays empt modelling-time / render-time split this ref opens with is **semantic, not physical** — same table, different `PermissionName`, key shape, and enforcement points. -**Gate the storefront by role string.** Rows scoped to the frontend role strings `Anonymous` / -`AuthenticatedFrontend` gate correctly; rows scoped to an `AccessUser` group id failed to gate the -frontend on a 10.26.x build. Design storefront gates on the role strings, keep group-scoped -visibility on the surfaces that natively support it (Assortments, DC groups — -[`commerce-b2b.md`](commerce-b2b.md)), and validate any group-scoped page gate on the target build -before a demo depends on it. +**Group-scoped gates need the deny+grant pair.** Rows scoped to the frontend role strings +`Anonymous` / `AuthenticatedFrontend` gate correctly on their own. A **bare** group-id grant does +NOT gate — highest-wins resolution lets the inherited broad `AuthenticatedFrontend` grant override +it, which reads as "group gating is non-functional" if that's the only shape tested. The working +shape (verified live on 10.26.x, page AND paragraph level): an explicit +`AuthenticatedFrontend → None` deny **plus** a ` → Read` grant **on the same entity** — +i.e. exactly the two-step recipe under "Frontend resolution" below. For visibility that should +follow commerce data rather than CMS permissions, prefer the surfaces that natively scope by group +(Assortments, DC groups — [`commerce-b2b.md`](commerce-b2b.md)). ### Enforcement points @@ -673,11 +676,14 @@ as "blank homepage", not "please sign in". `Page.PermissionType = 0` keeps a page inheriting rather than carrying its own rows). 2. No template edits needed. Nav, redirect, and child-render all self-filter. -### How to hide a single paragraph from a role +### How to hide a single paragraph from a persona -Use the paragraph's own Permissions panel — same entity-store mechanics; the frontend renderer's -`Content.cs:398` returns empty content for users without a read grant. (Only the `PermissionName='Page'` -row shape is live-verified; drive paragraph gates through the panel rather than hand-writing rows.) +Same entity-store mechanics with `PermissionName='Paragraph'` and the paragraph id as +`PermissionKey` (live-verified on 10.26.x): write the deny+grant pair — +`('AuthenticatedFrontend', '', 'Paragraph', )` plus +`('', '', 'Paragraph', )` — via the paragraph's Permissions panel or +direct SQL + security-cache flush. The frontend renderer's `Content.cs:398` returns empty content +for users without a read grant. ### Frontend resolution takes the HIGHEST level across a user's identities @@ -687,8 +693,8 @@ is ignored. Resolution takes the **highest** level across all of a user's identi grants `Read`. To hide a subtree from one persona while keeping it for others: 1. Deny the broad role on the subtree root (e.g. `AuthenticatedFrontend → None`). -2. Grant the personas that *should* keep it → `Read` — and per the role-string rule above, verify a - group-scoped grant actually gates on the target build before relying on it. +2. Grant the personas that *should* keep it → `Read` (group-id grants work here — the explicit + deny in step 1 is what makes them effective; a group grant without it is silently overridden). The excluded persona then resolves to `None` (section drops from all nav templates, direct URLs 302); others keep it; children inherit the root. (When a CSR-type user **impersonates** a customer the diff --git a/skills/dw-demo-swift/references/dw10-canonical-surfaces.md b/skills/dw-demo-swift/references/dw10-canonical-surfaces.md index 4a144d8..a016b85 100644 --- a/skills/dw-demo-swift/references/dw10-canonical-surfaces.md +++ b/skills/dw-demo-swift/references/dw10-canonical-surfaces.md @@ -15,7 +15,7 @@ | Surface you're about to hand-roll | Use instead — owned by | Candidate | |---|---|---| | Read user / user groups (`Pageview.User`, `Pageview.User.GetGroups()`) | `dw-render-viewmodels` | [`render-viewmodels.md`](../../dw-demo-base/references/foundational/render-viewmodels.md) "User identity / groups" | -| Gate a Page/Paragraph by role (the permission entity store — `UnifiedPermission` rows keyed `PermissionName='Page'`, NOT the legacy `*Permission` columns) | `dw-users-permissions` | [`users-permissions.md`](../../dw-demo-base/references/foundational/users-permissions.md) §15 | +| Gate a Page/Paragraph by role or group (the permission entity store — `UnifiedPermission` rows keyed `PermissionName='Page'`/`'Paragraph'`; group gates need the broad-role deny pair; NOT the legacy `*Permission` columns) | `dw-users-permissions` | [`users-permissions.md`](../../dw-demo-base/references/foundational/users-permissions.md) §15 | | Read prices (`Services.Prices`, custom `PriceProvider`) | `dw-commerce-catalog` | [`commerce-catalog.md`](../../dw-demo-base/references/foundational/commerce-catalog.md) §2.12 | | Read customer orders (`Services.Orders.GetCustomerOrdersByType` / `GetOrdersBySearch`) | `dw-commerce-orders` | [`commerce-orders.md`](../../dw-demo-base/references/foundational/commerce-orders.md) "canonical order read surface" | | Get product / friendly URLs; `AddStylesheet`/`AddScript` hoisting; cross-cutting redirects (`Page.Loaded` subscriber); per-category behavior; product-field arrays | `dw-render-razor` | [`render-razor.md`](../../dw-demo-base/references/foundational/render-razor.md) §1 | diff --git a/skills/dw-users-permissions/SKILL.md b/skills/dw-users-permissions/SKILL.md index 68bf8ac..149b7c3 100644 --- a/skills/dw-users-permissions/SKILL.md +++ b/skills/dw-users-permissions/SKILL.md @@ -154,9 +154,10 @@ Default: Anonymous users have Read on all pages. To restrict a page or branch: 1. Set **Anonymous users (frontend) → None** on the page -2. Grant **Read** to **Authenticated users (frontend)** — role grants gate reliably. Verify a - group-scoped page grant actually gates on the target build before relying on it (group-scoped - rows failed to gate the frontend on a 10.26.x install; role-string rows gated correctly). +2. Grant **Read** to **Authenticated users (frontend)** — or, to restrict to specific groups, set + **Authenticated users (frontend) → None** and grant **Read** to the target groups. The explicit + broad-role deny is load-bearing: a bare group grant is silently overridden by the inherited + Authenticated-users grant (highest wins) and does not gate. A denied anonymous visitor is auto-redirected to the first page in the website that carries the UserAuthentication app — keep that page active and un-restricted.