Repository navigation
fix(admin): offer purge controls to admins who may only purge - #257
Merged
Merged
Conversation
Every purge control decided whether to render from GET /rest-cache/config/strategy, which requires cache.read-strategy. An admin granted cache.purge without it got a 403 there, so the list-view button and the edit-view action both hid themselves, and the only thing the permission is for was nowhere in the panel. The server would have accepted the purge. Relaxing the strategy route would have handed the whole resolved strategy - hitpass, keys, routes - to anyone allowed to purge. Instead, add GET /rest-cache/purge/content-types, gated by cache.purge, returning only the cached uids. That reveals nothing cache.purge did not already: POST /purge answers 400 for any uid that is not cached. The admin asks it only after the strategy request comes back 403, so an admin who can read the strategy makes exactly the requests they made before and sees exactly the same controls. An admin with neither permission gets a 403 from both and sees nothing, as before. The edit view's cache panel still needs the strategy - its content is the max age - so it stays hidden for a purge-only admin. The 403 check reads `status` as well as `response.status`: the host's FetchError puts the code on the former.
7 of 8 tasks
Two claims in this change had nothing holding them. The edit-view panel's `!config` guard: without it a purge-only admin is eligible, has no strategy, and the panel reads "cached for up to - after they are first requested". The edit-view spec now asserts the panel is absent once the purge action has proved the admin eligible. Seen red with the guard removed: the sentence above rendered and the count was 1. "An admin who can read the strategy makes exactly the same requests as before": a new spec records every request the super admin makes across the list and edit views and asserts /rest-cache/purge/content-types is never among them, after checking the strategy request was recorded so it cannot pass on an empty log. Seen red with the fallback's `enabled` forced to true: the endpoint was requested twice.
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.
What changed and why
The failure
An admin granted Purge cache (
plugin::rest-cache.cache.purge) but not Read cache strategy configuration (cache.read-strategy) was never offered a purge control.Every purge control in the content manager (the list-view Purge REST Cache button and the edit-view document action) decided whether to render by reading
GET /rest-cache/config/strategyand checking that the content type is in it. That route requirescache.read-strategy. For this admin it answers 403,datastays undefined, and every control returnsnull. The server would have accepted the purge:POST /rest-cache/purgeonly needscache.purge. So the role worked over the API and did nothing in the panel.The fix
Without relaxing the strategy route, which carries the whole resolved strategy (hitpass, keys, routes) and is more than someone allowed to purge needs to see:
GET /rest-cache/purge/content-types, gated byadmin::isAuthenticatedAdmin+cache.purge(the same policy asPOST /purge). Returns{ "contentTypes": ["api::article.article", ...] }and nothing else. It reveals nothingcache.purgedid not already:POST /purgeanswers400 contentType is not cachedfor any uid outside the strategy, so the list was already discoverable one uid at a time. The response typePurgeableContentTypesResponseis documented as "deliberately only the uids" so it does not grow into a less-guarded copy ofStrategyResponse.usePurgeableContentTypes()inservices/restCache.tsreturns the uids from the strategy when it loads, and only after the strategy request comes back 403 asks the new endpoint.useEndpointgained anenabledflag so the fallback hook can be called unconditionally but not fetch.PurgeCacheButtonanduseCachedDocument().isEligibleread from it.if (!isEligible || !config) return null). The Playwright spec asserts it is absent.useFetchClientonly. NoadminApi,useRBACorPage.Protect. The permission is derived from the 403, per AGENTS.md.error.statusas well aserror.response.status. The host'sFetchErrorsets the code onstatus, and itsresponseis{ data }with no status.Why this is not breaking
GET /rest-cache/config/strategy,/stats,/config/providerandPOST /purge: unchanged routes, policies and responses. No permission is widened. The jest spec asserts the strategy route still answers 403 to the purge-only admin, and the Playwright spec asserts the panel saw that 403.isEligibleis computed from the same uid list the strategy gives.cache.read-strategybut notcache.purgeis unchanged too: still shown the control, still refused by the server with a 403 notification.Cache-Controlor bypass is touched.Tests
shared/tests/admin-purge-permission.test.js(jest). Creates two admins through the admin API, one holding onlycache.purgeand one holding no plugin permission, and asserts:cache.purge;cache.purge, the body has exactly one keycontentTypes, and it equalscacheConfig.getUids();GET /rest-cache/config/strategyis still 403 for the purge-only admin.e2e/tests/purge-permission.spec.ts(Playwright). Creates a role withcontent-manager.explorer.readon articles +cache.purge, registers a user, logs in as them, and asserts:A second describe block logs in as the super admin (who holds
cache.read-strategy), records every request across the list and edit views, and asserts/rest-cache/purge/content-typesis never requested. It first checks that the strategy request was recorded, so it cannot pass on an empty log.Before (unfixed
main), jest:Before (unfixed
main), Playwright:Test 1 got past
expect((await strategy).status()).toBe(403)before failing, so the 403 was there onmainand is still there after the fix.Each new assertion, seen red on purpose:
With the panel's
!configguard removed (if (!isEligible) return null), the purge-only admin's edit view rendered "Responses for this entry are cached for up to - after they are first requested.":With the fallback hook's
enabledforced totrue:After:
Closes #
Checklist
shared/tests/that fails before the change. Browser-visible behaviour goes ine2e/tests/.pnpm run test:e2e:memorypasses. (24 suites, 139 tests)pnpm run test:lintpasses (tsc --noEmitoverserver/andadmin/, plus eslint and prettier on the providers).admin/?pnpm run test:adminwas run and passes. (16 passed)docs/guide/updated in this PR: the route is inreference/routes.md, andadmin/index.mdsays what a purge-only admin sees.main.Notes for the reviewer
/purge/content-types, under the purge routes, because it belongs to the purge permission rather than toconfig/*.useCachedDocument.tsandPurgeCacheButton/index.tsx, in different hunks.git merge-treereports a clean merge.SettingsPage's ownisForbiddenreads onlyerror.response.status, which the host'sFetchErrordoes not set, so a 403 there would render the generic error page instead ofPage.NoPermissions. The settings link is permission-gated by the admin, so this is hard to reach. I left it alone.