Files
breadcrumb-the-shire/pages/admin/departments/create().php
fs fd90065048 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>
2026-03-10 15:40:07 +01:00

100 lines
3.9 KiB
PHP

<?php
use MintyPHP\Buffer;
use MintyPHP\Http\SessionStoreInterface;
use MintyPHP\Router;
use MintyPHP\Service\Access\DepartmentAuthorizationPolicy;
use MintyPHP\Session;
use MintyPHP\Support\Flash;
use MintyPHP\Support\Guard;
$session = app(SessionStoreInterface::class)->all();
Guard::requireLogin();
$request = requestInput();
$tenantService = app(\MintyPHP\Service\Tenant\TenantService::class);
$directoryScopeGateway = app(\MintyPHP\Service\Directory\DirectoryScopeGateway::class);
$departmentService = app(\MintyPHP\Service\Org\DepartmentService::class);
$currentUserId = (int) ($session['user']['id'] ?? 0);
$authorizationService = app(\MintyPHP\Service\Access\AuthorizationService::class);
$decision = $authorizationService->authorize(DepartmentAuthorizationPolicy::ABILITY_ADMIN_DEPARTMENTS_CREATE, [
'actor_user_id' => $currentUserId,
]);
if (!$decision->isAllowed()) {
Guard::deny();
}
$capabilities = $decision->attribute('capabilities', []);
$allowedTenantIds = is_array($capabilities) ? array_values(array_unique(array_map('intval', (array) ($capabilities['allowed_tenant_ids'] ?? [])))) : [];
$isStrictScope = is_array($capabilities) ? (bool) ($capabilities['is_strict_scope'] ?? false) : false;
$errorBag = formErrors();
$errors = [];
$warnings = [];
$form = [
'description' => '',
'tenant_id' => 0,
'code' => '',
'cost_center' => '',
'active' => '1',
];
$tenants = $tenantService->list();
if ($allowedTenantIds) {
$tenants = array_values(array_filter($tenants, static function (array $tenant) use ($allowedTenantIds): bool {
$tenantId = (int) ($tenant['id'] ?? 0);
return $tenantId > 0 && in_array($tenantId, $allowedTenantIds, true);
}));
} elseif ($isStrictScope) {
$tenants = [];
}
if ($request->isMethod('POST') && !Session::checkCsrfToken()) {
Flash::error(t('Form expired, please try again'), 'admin/departments/create', 'csrf_expired');
Router::redirect('admin/departments/create');
return;
}
if ($request->hasBody('description')) {
$selectedTenantId = $request->bodyInt('tenant_id');
$selectedTenantIds = $directoryScopeGateway->filterTenantIdsForUser([$selectedTenantId], $currentUserId);
$selectedTenantId = (int) ($selectedTenantIds[0] ?? 0);
$input = $request->bodyAll();
$input['tenant_id'] = $selectedTenantId;
$result = $departmentService->createFromAdmin($input, $currentUserId);
$form = $result['form'] ?? $form;
$errorBag->merge($result['errors'] ?? []);
$warnings = $result['warnings'] ?? [];
$form['tenant_id'] = $selectedTenantId;
if (($result['ok'] ?? false) && !$errorBag->hasAny()) {
$action = (string) $request->body('action', 'create');
if ($action === 'create_close') {
if ($warnings) {
Flash::info(implode(' ', $warnings), 'admin/departments', 'department_warning');
}
Flash::success('Department created', 'admin/departments', 'department_created');
Router::redirect('admin/departments');
} else {
$uuid = (string) ($result['uuid'] ?? '');
if ($uuid !== '') {
$target = "admin/departments/edit/{$uuid}";
if ($warnings) {
Flash::info(implode(' ', $warnings), $target, 'department_warning');
}
Flash::success('Department created', $target, 'department_created');
Router::redirect($target);
} else {
if ($warnings) {
Flash::info(implode(' ', $warnings), 'admin/departments', 'department_warning');
}
Flash::success('Department created', 'admin/departments', 'department_created');
Router::redirect('admin/departments');
}
}
}
}
$validationSummaryErrors = $errorBag->toArray();
$errors = $errorBag->toFlatList();
Buffer::set('title', t('Create department'));