fix(security): add CSRF protection to all POST endpoints + contract tests (B2)

CSRF guards added to 16 admin pages that previously accepted POST requests
without token verification (GR-SEC-001): departments, permissions, roles,
settings, tenants, users (create/edit/bulk/tokens), and lang switch.

New contract tests:
- PostEndpointCsrfContractTest: scans all pages/ for POST handlers and
  verifies checkCsrfToken is present (API/data endpoints exempt).
- DatabaseUpdatesContractTest: validates db/updates/ scripts follow naming
  convention and use idempotent SQL patterns (GR-CORE-010).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 15:40:07 +01:00
parent 6286ec4179
commit fd90065048
18 changed files with 302 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Router;
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
use MintyPHP\Service\Settings\AdminSettingsService;
use MintyPHP\Session;
use MintyPHP\Support\Flash;
use MintyPHP\Support\Guard;
@@ -33,6 +34,12 @@ $settings = is_array($pageData['settings'] ?? null) ? $pageData['settings'] : []
$activeLoginTokens = (int) ($pageData['active_login_tokens'] ?? 0);
$activeApiTokens = (int) ($pageData['active_api_tokens'] ?? 0);
if ($request->isMethod('POST') && !Session::checkCsrfToken()) {
Flash::error(t('Form expired, please try again'), 'admin/settings', 'csrf_expired');
Router::redirect('admin/settings');
return;
}
if ($request->isMethod('POST')) {
$post = $request->bodyAll();
if (!array_key_exists('settings_submit', $post)) {