refactor(actions): centralize core page request/csrf guards
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"issue_summary": "Nach Abschluss von pages/admin gab es in non-admin Core-Seiten noch direkte requestInput()->method() und Session::checkCsrfToken()-Nutzung. Ziel war die gleiche Zentralisierung auch für pages/auth, pages/page, pages/lang und pages/flash.",
|
||||||
|
"affected_layers": ["pages", "tests"],
|
||||||
|
"affected_files": [
|
||||||
|
{ "path": "pages/lang().php", "role": "Locale switch endpoint (GET/POST mixed)" },
|
||||||
|
{ "path": "pages/flash/dismiss($id).php", "role": "Flash dismiss POST endpoint" },
|
||||||
|
{ "path": "pages/page/copy-language().php", "role": "Page content locale copy action" },
|
||||||
|
{ "path": "pages/page/index($slug).php", "role": "Page edit submit flow with optional JSON response" },
|
||||||
|
{ "path": "pages/auth/login().php", "role": "Multi-stage login action" },
|
||||||
|
{ "path": "pages/auth/register().php", "role": "Registration action" },
|
||||||
|
{ "path": "pages/auth/forgot().php", "role": "Password reset request action" },
|
||||||
|
{ "path": "pages/auth/verify().php", "role": "Password reset verification code action" },
|
||||||
|
{ "path": "pages/auth/verify-email().php", "role": "Email verification and resend action" },
|
||||||
|
{ "path": "pages/auth/reset().php", "role": "Password reset completion action" },
|
||||||
|
{ "path": "tests/Architecture/CorePageRequestGuardContractTest.php", "role": "Architecture contract to prevent legacy request/csrf checks in non-admin core pages" }
|
||||||
|
],
|
||||||
|
"risks_discovered": [
|
||||||
|
"Auth/page flows rely on in-page error handling instead of redirect on CSRF mismatch in several places; helper use must preserve this behavior.",
|
||||||
|
"Mixed GET/POST endpoints (lang, page/index) require conditional CSRF enforcement without breaking GET rendering paths."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"plan_ref": ".agents/runs/2026-04-24-action-csrf-centralization-step4/plan.json",
|
||||||
|
"status": "done",
|
||||||
|
"changed_files": [
|
||||||
|
{ "path": "pages/lang().php", "summary": "Replaced direct CSRF check with actionRequireCsrf while preserving redirect + flash behavior." },
|
||||||
|
{ "path": "pages/flash/dismiss($id).php", "summary": "Replaced direct method + CSRF checks with actionRequirePost/actionRequireCsrf." },
|
||||||
|
{ "path": "pages/page/copy-language().php", "summary": "Added method guard via actionRequirePost and replaced direct CSRF check with helper call." },
|
||||||
|
{ "path": "pages/page/index($slug).php", "summary": "Replaced direct method/CSRF checks with helper-compatible request/isMethod and actionRequireCsrf." },
|
||||||
|
{ "path": "pages/auth/login().php", "summary": "Replaced direct method/CSRF checks with requestInput()->isMethod and actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "pages/auth/register().php", "summary": "Replaced direct CSRF check with actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "pages/auth/forgot().php", "summary": "Replaced direct CSRF check with actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "pages/auth/verify().php", "summary": "Replaced direct CSRF check with actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "pages/auth/verify-email().php", "summary": "Replaced direct CSRF check with actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "pages/auth/reset().php", "summary": "Replaced direct CSRF check with actionRequireCsrf (non-redirect mode)." },
|
||||||
|
{ "path": "tests/Architecture/CorePageRequestGuardContractTest.php", "summary": "Added non-admin regression guard contract test." }
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpunit tests/Architecture/CorePageRequestGuardContractTest.php tests/Architecture/AdminActionRequestGuardContractTest.php", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpunit tests/Architecture/PostEndpointCsrfContractTest.php tests/Architecture/PostRedirectGetContractTest.php", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpunit tests/Architecture/AuthzAdminSettingsContractTest.php tests/Architecture/AuthzAdminMasterDataContractTest.php tests/Architecture/AuthzAdminUsersContractTest.php", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpunit", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpstan analyse -c phpstan.neon --no-progress", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/phpunit tests/Architecture/CoreStarterkitContractTest.php", "result": "pass" },
|
||||||
|
{ "cmd": "docker compose exec -T php vendor/bin/php-cs-fixer fix --config=tools/php-cs-fixer/.php-cs-fixer.dist.php --dry-run --diff --verbose", "result": "pass" },
|
||||||
|
{ "cmd": "bin/docs-link-check.sh", "result": "pass" },
|
||||||
|
{ "cmd": "bin/docs-drift-check.sh", "result": "pass" },
|
||||||
|
{ "cmd": "bin/codex-skills-sync.sh --check", "result": "pass" }
|
||||||
|
],
|
||||||
|
"quality_gate_results": [
|
||||||
|
{ "gate_id": "QG-001", "result": "pass", "notes": "PHPUnit full run green: 2027 tests, 29637 assertions." },
|
||||||
|
{ "gate_id": "QG-002", "result": "pass", "notes": "PHPStan level 5: no errors." },
|
||||||
|
{ "gate_id": "QG-003", "result": "pass", "notes": "CoreStarterkitContractTest green: 13 tests, 6986 assertions." },
|
||||||
|
{ "gate_id": "QG-006", "result": "pass", "notes": "php-cs-fixer dry-run green." },
|
||||||
|
{ "gate_id": "QG-008", "result": "pass", "notes": "docs-link-check and docs-drift-check green." },
|
||||||
|
{ "gate_id": "QG-009", "result": "pass", "notes": "codex-skills-sync --check green." }
|
||||||
|
],
|
||||||
|
"metrics": {
|
||||||
|
"step4_target_files": 10,
|
||||||
|
"before_requestinput_method_calls": 3,
|
||||||
|
"after_requestinput_method_calls": 0,
|
||||||
|
"before_inline_session_csrf_checks": 10,
|
||||||
|
"after_inline_session_csrf_checks": 0,
|
||||||
|
"non_admin_inline_method_checks_total_after": 0,
|
||||||
|
"non_admin_inline_csrf_checks_total_after": 0
|
||||||
|
},
|
||||||
|
"open_items": [
|
||||||
|
"Module pages under modules/*/pages still contain inline method/csrf checks and are intentionally out-of-scope for this step."
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"ready_to_finalize": true,
|
||||||
|
"code_review": "pass",
|
||||||
|
"security_review": "pass",
|
||||||
|
"acceptance_review": "pass",
|
||||||
|
"ci_status": "required-gates-pass",
|
||||||
|
"final_action": "commit",
|
||||||
|
"hold_reason": "",
|
||||||
|
"commit_message": "refactor(actions): centralize core page request/csrf guards",
|
||||||
|
"notes": "Core non-admin pages now aligned with helper-based request and CSRF guard pattern."
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"analysis_ref": ".agents/runs/2026-04-24-action-csrf-centralization-step4/analysis.json",
|
||||||
|
"summary": "Complete centralization of legacy request method and CSRF checks for all remaining non-admin core pages.",
|
||||||
|
"scope": {
|
||||||
|
"in": [
|
||||||
|
"Migrate remaining pages/* (excluding pages/admin) off direct requestInput()->method() and Session::checkCsrfToken()",
|
||||||
|
"Preserve existing error/redirect behavior in auth and page-edit flows",
|
||||||
|
"Add non-admin architecture contract"
|
||||||
|
],
|
||||||
|
"out": [
|
||||||
|
"modules/*/pages",
|
||||||
|
"service/repository business logic changes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"success_criteria": [
|
||||||
|
{ "id": "SC-001", "criterion": "Remaining non-admin method checks use helper-compatible patterns (no direct requestInput()->method())." },
|
||||||
|
{ "id": "SC-002", "criterion": "Remaining non-admin CSRF checks use actionRequireCsrf (no direct Session::checkCsrfToken())." },
|
||||||
|
{ "id": "SC-003", "criterion": "New CorePageRequestGuardContractTest prevents regression in non-admin core pages." },
|
||||||
|
{ "id": "SC-004", "criterion": "Architecture + required gate set remains green." }
|
||||||
|
],
|
||||||
|
"required_quality_gate_ids": ["QG-001", "QG-002", "QG-003", "QG-006", "QG-008", "QG-009"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"verdict": "pass",
|
||||||
|
"checked_criterion_ids": [
|
||||||
|
"SC-001",
|
||||||
|
"SC-002",
|
||||||
|
"SC-003",
|
||||||
|
"SC-004"
|
||||||
|
],
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"criterion_id": "SC-001",
|
||||||
|
"criterion": "Remaining non-admin method checks use helper-compatible patterns (no direct requestInput()->method()).",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "Direct requestInput()->method() usage in non-admin pages reduced to 0."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"criterion_id": "SC-002",
|
||||||
|
"criterion": "Remaining non-admin CSRF checks use actionRequireCsrf (no direct Session::checkCsrfToken()).",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "Direct Session::checkCsrfToken() usage in non-admin pages reduced to 0."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"criterion_id": "SC-003",
|
||||||
|
"criterion": "New CorePageRequestGuardContractTest prevents regression in non-admin core pages.",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "CorePageRequestGuardContractTest added and passing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"criterion_id": "SC-004",
|
||||||
|
"criterion": "Architecture + required gate set remains green.",
|
||||||
|
"result": "pass",
|
||||||
|
"evidence": "All required gates and targeted architecture tests passed."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"missing_or_wrong": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"verdict": "pass",
|
||||||
|
"checked_guard_ids": [
|
||||||
|
"GR-CORE-003",
|
||||||
|
"GR-CORE-012",
|
||||||
|
"GR-TEST-001"
|
||||||
|
],
|
||||||
|
"checked_quality_gate_ids": [
|
||||||
|
"QG-001",
|
||||||
|
"QG-002",
|
||||||
|
"QG-003",
|
||||||
|
"QG-006",
|
||||||
|
"QG-008",
|
||||||
|
"QG-009"
|
||||||
|
],
|
||||||
|
"findings": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"task_id": "2026-04-24-action-csrf-centralization-step4",
|
||||||
|
"verdict": "pass",
|
||||||
|
"checked_guard_ids": [
|
||||||
|
"GR-SEC-001",
|
||||||
|
"GR-SEC-008",
|
||||||
|
"GR-SEC-009"
|
||||||
|
],
|
||||||
|
"findings": []
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ use MintyPHP\Buffer;
|
|||||||
use MintyPHP\Http\SessionStoreInterface;
|
use MintyPHP\Http\SessionStoreInterface;
|
||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -16,7 +15,7 @@ $email = trim((string) $request->body('email', ''));
|
|||||||
$passwordResetService = (app(AuthServicesFactory::class))->createPasswordResetService();
|
$passwordResetService = (app(AuthServicesFactory::class))->createPasswordResetService();
|
||||||
|
|
||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('password/forgot', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$errorBag->addGlobal(t('Form expired, please try again'));
|
$errorBag->addGlobal(t('Form expired, please try again'));
|
||||||
} elseif ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
} elseif ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
$errorBag->addGlobal(t('Please enter a valid email address'));
|
$errorBag->addGlobal(t('Please enter a valid email address'));
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use MintyPHP\Service\Auth\AuthServicesFactory;
|
|||||||
use MintyPHP\Service\Security\SecurityServicesFactory;
|
use MintyPHP\Service\Security\SecurityServicesFactory;
|
||||||
use MintyPHP\Service\Tenant\TenantServicesFactory;
|
use MintyPHP\Service\Tenant\TenantServicesFactory;
|
||||||
use MintyPHP\Service\User\UserServicesFactory;
|
use MintyPHP\Service\User\UserServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$requestRuntime = app(RequestRuntimeInterface::class);
|
$requestRuntime = app(RequestRuntimeInterface::class);
|
||||||
@@ -172,8 +171,8 @@ $resolveSelectedTenantId = static function (array $candidateByIdValue, int $requ
|
|||||||
return (int) ($first['id'] ?? 0);
|
return (int) ($first['id'] ?? 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (requestInput()->method() === 'POST') {
|
if (requestInput()->isMethod('POST')) {
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('login', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$errorBag->addGlobal(t('Form expired, please try again'));
|
$errorBag->addGlobal(t('Form expired, please try again'));
|
||||||
$stage = 'resolve_email';
|
$stage = 'resolve_email';
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use MintyPHP\Http\SessionStoreInterface;
|
|||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||||
use MintyPHP\Service\User\UserServicesFactory;
|
use MintyPHP\Service\User\UserServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -17,7 +16,7 @@ if (!allowRegistration()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('register', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$error = t('Form expired, please try again');
|
$error = t('Form expired, please try again');
|
||||||
} elseif ($request->hasBody('email')) {
|
} elseif ($request->hasBody('email')) {
|
||||||
$authService = (app(AuthServicesFactory::class))->createAuthService();
|
$authService = (app(AuthServicesFactory::class))->createAuthService();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use MintyPHP\Http\SessionStoreInterface;
|
|||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||||
use MintyPHP\Service\User\UserServicesFactory;
|
use MintyPHP\Service\User\UserServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -24,7 +23,7 @@ if ($resetId <= 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('password/reset', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$errorBag->addGlobal(t('Form expired, please try again'));
|
$errorBag->addGlobal(t('Form expired, please try again'));
|
||||||
} else {
|
} else {
|
||||||
$result = $passwordResetService->resetPassword($resetId, $password, $password2);
|
$result = $passwordResetService->resetPassword($resetId, $password, $password2);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use MintyPHP\Buffer;
|
|||||||
use MintyPHP\Http\SessionStoreInterface;
|
use MintyPHP\Http\SessionStoreInterface;
|
||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -18,7 +17,7 @@ $code = trim((string) $request->body('code', ''));
|
|||||||
$passwordResetService = (app(AuthServicesFactory::class))->createPasswordResetService();
|
$passwordResetService = (app(AuthServicesFactory::class))->createPasswordResetService();
|
||||||
|
|
||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('password/verify', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$errorBag->addGlobal(t('Form expired, please try again'));
|
$errorBag->addGlobal(t('Form expired, please try again'));
|
||||||
} elseif ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
} elseif ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
$errorBag->addGlobal(t('Please enter a valid email address'));
|
$errorBag->addGlobal(t('Please enter a valid email address'));
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use MintyPHP\Buffer;
|
|||||||
use MintyPHP\Http\SessionStoreInterface;
|
use MintyPHP\Http\SessionStoreInterface;
|
||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\Auth\AuthServicesFactory;
|
use MintyPHP\Service\Auth\AuthServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -20,7 +19,7 @@ $emailVerificationService = (app(AuthServicesFactory::class))->createEmailVerifi
|
|||||||
if ($request->isMethod('POST')) {
|
if ($request->isMethod('POST')) {
|
||||||
$action = (string) $request->body('action', 'verify');
|
$action = (string) $request->body('action', 'verify');
|
||||||
|
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf('verify-email', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
$errorBag->addGlobal(t('Form expired, please try again'));
|
$errorBag->addGlobal(t('Form expired, please try again'));
|
||||||
} elseif ($action === 'resend') {
|
} elseif ($action === 'resend') {
|
||||||
// Resend verification code
|
// Resend verification code
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
use MintyPHP\Http\Request;
|
use MintyPHP\Http\Request;
|
||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$target = Request::safeReturnTarget(requestInput()->bodyAll()['return'] ?? '');
|
$request = requestInput();
|
||||||
|
$target = Request::safeReturnTarget($request->bodyAll()['return'] ?? '');
|
||||||
|
|
||||||
if (requestInput()->method() !== 'POST') {
|
if (!actionRequirePost($target)) {
|
||||||
Router::redirect($target);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf($target, flashOnFailure: false)) {
|
||||||
Router::redirect($target);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$flashId = isset($id) ? trim((string) $id) : '';
|
$flashId = isset($id) ? trim((string) $id) : '';
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ use MintyPHP\Http\SessionStoreInterface;
|
|||||||
use MintyPHP\I18n;
|
use MintyPHP\I18n;
|
||||||
use MintyPHP\Router;
|
use MintyPHP\Router;
|
||||||
use MintyPHP\Service\User\UserServicesFactory;
|
use MintyPHP\Service\User\UserServicesFactory;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
$session = $sessionStore->all();
|
$session = $sessionStore->all();
|
||||||
|
|
||||||
$request = requestInput();
|
$request = requestInput();
|
||||||
if ($request->isMethod('POST') && !Session::checkCsrfToken()) {
|
if ($request->isMethod('POST') && !actionRequireCsrf('', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
Flash::error(t('Form expired, please try again'), '', 'csrf_expired');
|
Flash::error(t('Form expired, please try again'), '', 'csrf_expired');
|
||||||
Router::redirect('');
|
Router::redirect('');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use MintyPHP\Router;
|
|||||||
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
||||||
use MintyPHP\Service\Access\UiAccessService;
|
use MintyPHP\Service\Access\UiAccessService;
|
||||||
use MintyPHP\Service\Content\PageService;
|
use MintyPHP\Service\Content\PageService;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
@@ -21,6 +20,10 @@ if ($currentUserId <= 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$return = Request::safeReturnTarget((string) (requestInput()->bodyAll()['return'] ?? ''));
|
$return = Request::safeReturnTarget((string) (requestInput()->bodyAll()['return'] ?? ''));
|
||||||
|
if (!actionRequirePost($return !== '' ? $return : '')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$canEdit = app(UiAccessService::class)->allow(
|
$canEdit = app(UiAccessService::class)->allow(
|
||||||
$currentUserId,
|
$currentUserId,
|
||||||
SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE
|
SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_UPDATE
|
||||||
@@ -31,7 +34,7 @@ if (!$canEdit) {
|
|||||||
Router::redirect($return !== '' ? $return : '');
|
Router::redirect($return !== '' ? $return : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf($return !== '' ? $return : '', flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
Flash::error(t('Form expired, please try again'), $return, 'page_csrf');
|
Flash::error(t('Form expired, please try again'), $return, 'page_csrf');
|
||||||
Router::redirect($return !== '' ? $return : '');
|
Router::redirect($return !== '' ? $return : '');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ use MintyPHP\Router;
|
|||||||
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
use MintyPHP\Service\Access\SettingsAuthorizationPolicy;
|
||||||
use MintyPHP\Service\Access\UiAccessService;
|
use MintyPHP\Service\Access\UiAccessService;
|
||||||
use MintyPHP\Service\Content\PageService;
|
use MintyPHP\Service\Content\PageService;
|
||||||
use MintyPHP\Session;
|
|
||||||
use MintyPHP\Support\Flash;
|
use MintyPHP\Support\Flash;
|
||||||
|
|
||||||
$sessionStore = app(SessionStoreInterface::class);
|
$sessionStore = app(SessionStoreInterface::class);
|
||||||
$session = $sessionStore->all();
|
$session = $sessionStore->all();
|
||||||
|
$request = requestInput();
|
||||||
|
|
||||||
$slug = trim((string) ($slug ?? ''));
|
$slug = trim((string) ($slug ?? ''));
|
||||||
if ($slug === '') {
|
if ($slug === '') {
|
||||||
@@ -38,7 +38,7 @@ $returnPath = Request::path();
|
|||||||
if ($returnPath === '') {
|
if ($returnPath === '') {
|
||||||
$returnPath = 'page/' . $slug;
|
$returnPath = 'page/' . $slug;
|
||||||
}
|
}
|
||||||
$requestedReturn = Request::safeReturnTarget((string) (requestInput()->bodyAll()['return'] ?? ''));
|
$requestedReturn = Request::safeReturnTarget((string) ($request->bodyAll()['return'] ?? ''));
|
||||||
if ($requestedReturn !== '') {
|
if ($requestedReturn !== '') {
|
||||||
$returnPath = $requestedReturn;
|
$returnPath = $requestedReturn;
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ if (!empty($session['page_edit_mode']) && is_array($session['page_edit_mode']))
|
|||||||
$sessionStore->set('page_edit_mode', $pageEditMode);
|
$sessionStore->set('page_edit_mode', $pageEditMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestInput()->method() === 'POST') {
|
if ($request->isMethod('POST')) {
|
||||||
if (!$canEdit || $notFound) {
|
if (!$canEdit || $notFound) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
Flash::error(t('You are not allowed to edit this page'), $returnPath, 'page_forbidden');
|
Flash::error(t('You are not allowed to edit this page'), $returnPath, 'page_forbidden');
|
||||||
@@ -72,7 +72,7 @@ if (requestInput()->method() === 'POST') {
|
|||||||
Router::redirect($returnPath);
|
Router::redirect($returnPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Session::checkCsrfToken()) {
|
if (!actionRequireCsrf($returnPath, flashOnFailure: false, redirectOnFailure: false)) {
|
||||||
Flash::error(t('Form expired, please try again'), $returnPath, 'page_csrf');
|
Flash::error(t('Form expired, please try again'), $returnPath, 'page_csrf');
|
||||||
if ($wantsJson) {
|
if ($wantsJson) {
|
||||||
Router::json(['ok' => false, 'error' => 'csrf', 'redirect' => $returnPath]);
|
Router::json(['ok' => false, 'error' => 'csrf', 'redirect' => $returnPath]);
|
||||||
@@ -80,7 +80,7 @@ if (requestInput()->method() === 'POST') {
|
|||||||
Router::redirect($returnPath);
|
Router::redirect($returnPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = (string) (requestInput()->bodyAll()['content'] ?? '');
|
$content = (string) ($request->bodyAll()['content'] ?? '');
|
||||||
$result = PageService::updateContentBySlug($slug, $content, $currentUserId, I18n::$locale ?? null);
|
$result = PageService::updateContentBySlug($slug, $content, $currentUserId, I18n::$locale ?? null);
|
||||||
|
|
||||||
if (!($result['ok'] ?? false)) {
|
if (!($result['ok'] ?? false)) {
|
||||||
@@ -112,6 +112,6 @@ if ($contentJson === '') {
|
|||||||
$contentJson = json_encode(['blocks' => []], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
$contentJson = json_encode(['blocks' => []], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
$editMode = $canEdit && (($sessionEdit === true) || ((requestInput()->queryAll()['edit'] ?? '') === '1'));
|
$editMode = $canEdit && (($sessionEdit === true) || (($request->queryAll()['edit'] ?? '') === '1'));
|
||||||
$locales = defined('APP_LOCALES') ? APP_LOCALES : [I18n::$defaultLocale];
|
$locales = defined('APP_LOCALES') ? APP_LOCALES : [I18n::$defaultLocale];
|
||||||
$currentLocale = I18n::$locale ?? (I18n::$defaultLocale ?? 'de');
|
$currentLocale = I18n::$locale ?? (I18n::$defaultLocale ?? 'de');
|
||||||
|
|||||||
56
tests/Architecture/CorePageRequestGuardContractTest.php
Normal file
56
tests/Architecture/CorePageRequestGuardContractTest.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MintyPHP\Tests\Architecture;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
class CorePageRequestGuardContractTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProjectFileAssertionSupport;
|
||||||
|
|
||||||
|
public function testCorePagesDoNotInlineLegacyMethodChecks(): void
|
||||||
|
{
|
||||||
|
foreach ($this->corePageFiles() as $file) {
|
||||||
|
$content = $this->readProjectFile($file);
|
||||||
|
$this->assertStringNotContainsString('requestInput()->method()', $content, $file);
|
||||||
|
$this->assertStringNotContainsString('(requestInput()->method())', $content, $file);
|
||||||
|
$this->assertStringNotContainsString("strtoupper((string) (requestInput()->method()))", $content, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCorePagesDoNotInlineLegacyCsrfChecks(): void
|
||||||
|
{
|
||||||
|
foreach ($this->corePageFiles() as $file) {
|
||||||
|
$content = $this->readProjectFile($file);
|
||||||
|
$this->assertStringNotContainsString('Session::checkCsrfToken()', $content, $file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
private function corePageFiles(): array
|
||||||
|
{
|
||||||
|
$pagesRoot = $this->projectRootPath() . '/pages';
|
||||||
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pagesRoot));
|
||||||
|
$files = [];
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if (!$file->isFile() || $file->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativePath = 'pages/' . ltrim(str_replace($pagesRoot, '', $file->getPathname()), '/');
|
||||||
|
if (str_starts_with($relativePath, 'pages/admin/')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$files[] = $relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
sort($files);
|
||||||
|
$this->assertNotEmpty($files, 'Expected non-admin core pages.');
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user