Sorry, you do not have permission for that! You are logged in as a root administrator. Nothing is wrong with your ACP restrictions, and that is exactly where the message sends you to look.
The cause is CSRF, not permissions
IPS\Dispatcher\Admin runs this before dispatching:
if ( !isset( $this->classname::$csrfProtected ) and array_diff( array_keys( Request::i()->url()->queryString ), array( 'app', 'module', 'controller', 'id' ) ) ) { Session::i()->csrfCheck(); } Read it carefully. Any AdminCP URL carrying a query parameter other than app, module, controller or id is CSRF-checked automatically — unless the controller class declares $csrfProtected. And a failed CSRF check reports HTTP 403 with the language string admin_csrf_error, which renders as the permission message above.
So this is fine:
app=myapp&module=main&controller=things ← works app=myapp&module=main&controller=things&id=4 ← works, id is allowed and every one of these 403s:
...&controller=things&do=form ← "do" is not on the list ...&controller=things&do=settings ...&controller=report&days=90 ← your own filter links ...&controller=things&page=2 The fix
Declare the property. Its value is irrelevant — the dispatcher only calls isset() on it. It means "this controller takes responsibility for its own CSRF checks".
class things extends \IPS\Dispatcher\Controller { public static bool $csrfProtected = TRUE; // ... } Then call Session::i()->csrfCheck() yourself on the methods that actually change something — delete, toggle, reorder — and link to those with ->csrf() on the URL. Read-only screens such as a form or a report do not need a token.
Invision Community's own code declares this on essentially every AdminCP controller it ships. It is easy to miss precisely because it is boilerplate that nothing reminds you about.
Why nothing catches this before release
The failure is invisible to every check short of clicking the link:
The app installs cleanly. The menu entry appears. The default screen — no extra query parameters — renders perfectly. Rendering the form's fields in a test harness works, because the harness never goes through the dispatcher. Nothing is written to any log. Only a real HTTP request to a real do= URL as a signed-in administrator reveals it. If you have a screen-rendering test, make it fetch the actual AdminCP URLs rather than constructing the form objects.
A trap while you are testing this
Do not "fix" it by appending csrfKey to the URL. In developer mode Output::sendOutput() deliberately fatals on any 2xx response whose URL still contains a CSRF key:
An 200 response is being sent however the CSRF key is present in the requested URL. CSRF keys should be sent via POST or the request should be redirected to a URL not containing a CSRF key once finished. So without the property you get a 403, and with a token in the URL you get a 500. Both symptoms point away from the one-line cause.
Related: A stray "menutab_system" in your AdminCP: acpmenu.json tabs are not validated, Raw language keys in the AdminCP menu: acpmenu.json must be an object, What you can and cannot test from the command line
- 0 comments
- 43 views