Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NotFoundException is using the error500 template instead of the error400 template. #17599

Open
fr3nch13 opened this issue Feb 28, 2024 · 19 comments
Labels
Milestone

Comments

@fr3nch13
Copy link
Contributor

fr3nch13 commented Feb 28, 2024

Description

I noticed that something changed between 5.04 and 5.05 that is now causing the \Cake\Http\Exception\NotFoundException to use the error500 template instead of the error400 template, at least in my unit tests. I have confirmed the error message that is associated with the specific 404 error is there, it's just using the wrong template.

I would open a PR, but I still haven't been able to track down exactly which part of the code changed, or if I have something in my code that could be causing this. I also went through the changeling between 5.04 and 5.05, and didn't see anything that stood out that would cause this.

As a background, this is a new application, written only on CakePHP 5, so it's not ported from 4.x

CakePHP Version

5.04 ... 5.05

PHP Version

8.2.14

@fr3nch13 fr3nch13 changed the title NotFoundException is emitting the error500 template instead of the err400 template. NotFoundException is emitting the error500 template instead of the error400 template. Feb 28, 2024
@fr3nch13 fr3nch13 changed the title NotFoundException is emitting the error500 template instead of the error400 template. NotFoundException is using the error500 template instead of the error400 template. Feb 28, 2024
@dereuromark dereuromark added this to the 5.0.6 milestone Feb 28, 2024
@dereuromark
Copy link
Member

Any idea which change it would be? 5.0.4...5.0.5

@fr3nch13
Copy link
Contributor Author

My best guess is the ErrorHandlerMiddleware, but I don't know enough about the Middleware internals to know for sure. I'll dump a trace for you. Maybe it'll give a clue.

5.0.4...5.0.5#diff-aff0c9a62d0ee25e99d5331845f5bceddbad2223546c1c4fdc97f4f85feb7359R149

return new Response(['body' => $response, 'status' => 500]);

@fr3nch13
Copy link
Contributor Author

fr3nch13 commented Feb 28, 2024

The Cake\Http\Exception\NotFoundException is getting thrown, and the HTTP Response Code is a 404, it's just using the wrong template. Maybe you'll see something that I'm not? ¯_(ツ)_/¯

Edit: To add, this is just 1 example. Every Cake\Http\Exception\NotFoundException 404 error is doing this in the unit tests.

This is the full src/Policy/AppControllerPolicy.php:

<?php
declare(strict_types=1);

namespace App\Policy;

use App\Controller\AppController;
use Authorization\IdentityInterface;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\ResultInterface;
use Cake\Core\Configure;
use Cake\Http\Exception\NotFoundException;

/**
 * Base Controller policy
 */
class AppControllerPolicy implements BeforePolicyInterface
{
    /**
     * Summary of before
     *
     * @param ?\Authorization\IdentityInterface $identity The user or null
     * @param mixed $resource The controller
     * @param string $action The name of the action trying to be accessed
     * @return \Authorization\Policy\ResultInterface|bool|null
     * @throws \Cake\Http\Exception\NotFoundException if the action doesn't exist.
     */
    public function before(?IdentityInterface $identity, mixed $resource, string $action): ResultInterface|bool|null
    {
        // throw a 404 here for all missing actions.
        if (!empty($action) && $resource instanceof AppController) {
            if (!method_exists($resource, $action)) {
                $message = __('Page Not Found');
                if (Configure::read('debug')) {
                    $message = __('Missing Action `{0}::{1}()`', [
                        get_class($resource),
                        $action,
                    ]);
                }

                throw new NotFoundException($message);
            }
        }

        // fall through
        // always return null so that the other, more specific policy checks can happen.
        return null;
    }
}

These traces are from the cli-error.log during unit testing

This is with Debug on:


Request URL: /admin/qr-codes/dontexist
2024-02-28 20:01:18 error: [Cake\Http\Exception\NotFoundException] Missing Action `App\Controller\Admin\QrCodesController::dontexist()` in [APP_ROOT]/src/Policy/AppControllerPolicy.php on line 40
Stack Trace:
- [APP_ROOT]/vendor/cakephp/authorization/src/AuthorizationService.php:91
- [APP_ROOT]/vendor/cakephp/authorization/src/AuthorizationService.php:67
- [APP_ROOT]/src/Model/Entity/User.php:119
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:142
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:116
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:70
- [APP_ROOT]/src/Controller/AppController.php:87
- [APP_ROOT]/src/Controller/Admin/QrCodesController.php:64
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventManager.php:329
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventManager.php:307
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php:88
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/Controller.php:597
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:129
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:114
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/BaseApplication.php:332
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:86
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php:95
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php:258
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:116
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:87
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:169
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php:157
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:118
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:69
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:115
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:67
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Server.php:103
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:141
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:499
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:385
- [APP_ROOT]/tests/TestCase/Controller/Admin/QrCodes/PolicyTest.php:52
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:1122
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:654
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestRunner.php:104
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:488
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestSuite.php:340
- [APP_ROOT]/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:63
- [APP_ROOT]/vendor/phpunit/phpunit/src/TextUI/Application.php:198
- [APP_ROOT]/vendor/phpunit/phpunit/phpunit:104
- [APP_ROOT]/vendor/bin/phpunit:122
- [main]:

This is with Debug Off:

Request URL: /admin/qr-codes/dontexist
2024-02-28 20:01:18 error: [Cake\Http\Exception\NotFoundException] Page Not Found in [APP_ROOT]/src/Policy/AppControllerPolicy.php on line 40
Stack Trace:
- [APP_ROOT]/vendor/cakephp/authorization/src/AuthorizationService.php:91
- [APP_ROOT]/vendor/cakephp/authorization/src/AuthorizationService.php:67
- [APP_ROOT]/src/Model/Entity/User.php:119
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:142
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:116
- [APP_ROOT]/vendor/cakephp/authorization/src/Controller/Component/AuthorizationComponent.php:70
- [APP_ROOT]/src/Controller/AppController.php:87
- [APP_ROOT]/src/Controller/Admin/QrCodesController.php:64
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventManager.php:329
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventManager.php:307
- [APP_ROOT]/vendor/cakephp/cakephp/src/Event/EventDispatcherTrait.php:88
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/Controller.php:597
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:129
- [APP_ROOT]/vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:114
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/BaseApplication.php:332
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:86
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php:95
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php:258
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:116
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:87
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:169
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php:157
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:118
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:69
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:115
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:82
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Runner.php:67
- [APP_ROOT]/vendor/cakephp/cakephp/src/Http/Server.php:103
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:141
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:499
- [APP_ROOT]/vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:385
- [APP_ROOT]/tests/TestCase/Controller/Admin/QrCodes/PolicyTest.php:59
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:1122
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:654
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestRunner.php:104
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestCase.php:488
- [APP_ROOT]/vendor/phpunit/phpunit/src/Framework/TestSuite.php:340
- [APP_ROOT]/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:63
- [APP_ROOT]/vendor/phpunit/phpunit/src/TextUI/Application.php:198
- [APP_ROOT]/vendor/phpunit/phpunit/phpunit:104
- [APP_ROOT]/vendor/bin/phpunit:122
- [main]:

@dereuromark
Copy link
Member

dereuromark commented Apr 7, 2024

Could this be related to #17655 ?
And the error404 not found in some Admin/ prefixed path, since you are also using that
Then it triggers a 5xx error.

I am sure the core doesn't use such prefix error tests, so it went unnoticed.

@fr3nch13
Copy link
Contributor Author

fr3nch13 commented Apr 7, 2024

Yeah, that may be the issue, looking under a prefix. I'll go back, look at my code, and see if a prefix is what was causing it.

@markstory
Copy link
Member

This could be related to template paths? There was a recent change made to template paths in 59ca3e9 to address #17025

@dereuromark
Copy link
Member

Interesting. That was already in 4.5
But yeah. That looks exactly like the issue here.

@fr3nch13
Copy link
Contributor Author

So an update.

It doesn't seem to be caused by prefixes or plugins as the PagesController is in the app, not in a plugin.
It gives a 404 response code, but uses the error500.php template.
It only seems to be affecting the error400/error500, and only affects the tests where I have debug turned off.

The tests like so don't have prefixes in them:

// tests/TestCase/Controller/PagesControllerTest.php

class PagesControllerTest extends BaseControllerTest
{
    /**
     * Test that missing template renders 404 page in production
     *
     * @return void
     */
    public function testMissingTemplate()
    {
        Configure::write('debug', false); // being explicit here
        $this->get('https://localhost/pages/not_existing');
        $this->assertResponseCode(404);
        $this->helperTestError400('/pages/not_existing'); <--- fails here
    }
}

// tests/TestCase/Controller/BaseControllerTest.php

// this is just mainly helper functions to stay DRY.
// things like fixtures defined and helper functions like below.
// nothing that would invoke a change in the environment other than defining fixtures.
class BaseControllerTest extends TestCase
{
    use IntegrationTestTrait;

    /**
     * Fixtures
     *
     * @var array<string>
     */
    protected array $fixtures = [
        'app.Users',
        'app.Sources',
        'app.Tags',
        'app.QrCodes',
        'app.QrImages',
        'app.QrCodesTags',
    ];

    /**
     * @var \App\Model\Table\UsersTable
     */
    public $Users;

//// other helper fuctions

    /**
     * Tests that we're using the Error/error400 page
     *
     * @param ?string $path If included, also look for the actual error path as well.
     * @return void
     */
    public function helperTestError400(?string $path = null): void
    {
        $this->helperTestLayoutError();
        $content = (string)$this->_response->getBody();

        /// It's using App.Error/error500
        // I have a view helper that extends the Cake\View\Helper, and writes out the template being used.
        // That helper is writing "<!-- START: App.Error/error500 -->" and using that template.
        $this->assertSame(1, substr_count($content, '<!-- START: App.Error/error400 -->')); <--- Failing here.
        $this->assertSame(1, substr_count($content, '<!-- END: App.Error/error400 -->'));

        if ($path) {
            $this->assertSame(1, substr_count($content, 'The requested address <strong>\'' . $path . '\'</strong> was not found.'));
        }

        // test other specific to this layout.
    }
}

/// in src/View/Helper/TemplateHelper.php


    /**
     * Writes the template location coment.
     *
     * @param bool $start If were the start, or the end
     * @param string $path Absolute path to the Template.
     * @param string $prefix The prefix, mainly App, or a plugin identifier
     * @return string
     */
    public function templateComment(bool $start, string $path, string $prefix = 'App'): string
    {
        $comment = "\n\n" . '<!-- ' . ($start ? 'START' : 'END') . ': ' . $prefix . '.';

        $path = str_replace(ROOT . DS . 'templates' . DS, '', $path);
        $path = str_replace('.php', '', $path);

        $comment .= $path . ' -->' . "\n\n";

        return $comment;
    }

// in templates/Error/error400.php, error500.php, and others.


use Cake\Core\Configure;
use Cake\Error\Debugger;

$this->layout = 'error';
?>
<?= $this->Template->templateComment(true, __FILE__); ?>

With the same test above, but debug as true, it fails as a 500 instead of a 404, and the response body it gives this as expected:

<body>
    <header>
                <h1 class="header-title">
            <span>Template file <code>`Pages/not_existing.php`</code> could not be found.</span>
            <a>&#128203</a>
        </h1>
                    <span class="header-description"><br />
The following paths were searched:<br />
<br />
- <code>`[ROOT redacted]/templates/Pages/not_existing.php`</code><br />
- <code>`[ROOT redacted]/vendor/cakephp/cakephp/templates/Pages/not_existing.php`</code></span>
                <span class="header-type">Cake\View\Exception\MissingTemplateException</span>
    </header>

It was definitely as change from version 5.0.4 to 5.0.5, but I can't seem to track down which change it was.
It's also still present it the 5.x branch, and even in the 5.next branch.

I would submit a patch/tests/PR, but I can't seem what would be causing it.

If it helps, I can make my git repo plublic, or give one of you access to it.

@fr3nch13
Copy link
Contributor Author

Here is the stack traces from the log file if it helps:

This is the stack trace from debug turned off:

==> cli-error.log <==
2024-04-15 21:19:21 error: [Cake\Http\Exception\NotFoundException] Not Found in src/Controller/PagesController.php on line 85
Stack Trace:
- vendor/cakephp/cakephp/src/Controller/Controller.php:498
- vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:139
- vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:114
- vendor/cakephp/cakephp/src/Http/BaseApplication.php:332
- vendor/cakephp/cakephp/src/Http/Runner.php:86
- vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php:95
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php:258
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:116
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:87
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:159
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php:157
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:118
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:69
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:115
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Runner.php:67
- vendor/cakephp/cakephp/src/Http/Server.php:103
- vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:141
- vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:499
- vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:385
- tests/TestCase/Controller/PagesControllerTest.php:96
- vendor/phpunit/phpunit/src/Framework/TestCase.php:1160
- vendor/phpunit/phpunit/src/Framework/TestCase.php:659
- vendor/phpunit/phpunit/src/Framework/TestRunner.php:105
- vendor/phpunit/phpunit/src/Framework/TestCase.php:493
- vendor/phpunit/phpunit/src/Framework/TestSuite.php:349
- vendor/phpunit/phpunit/src/TextUI/TestRunner.php:62
- vendor/phpunit/phpunit/src/TextUI/Application.php:198
- vendor/phpunit/phpunit/phpunit:104
- vendor/bin/phpunit:122
- [main]:

And the same with debug turned on

Request URL: /pages/not_existing
2024-04-15 21:19:21 error: [Cake\View\Exception\MissingTemplateException] Template file `Pages/not_existing.php` could not be found.

The following paths were searched:

- `templates/Pages/not_existing.php`
- `vendor/cakephp/cakephp/templates/Pages/not_existing.php`
 in vendor/cakephp/cakephp/src/View/View.php on line 1377
Exception Attributes: array (
  'file' => 'Pages/not_existing.php',
  'paths' => 
  array (
    0 => 'templates/',
    1 => 'vendor/cakephp/cakephp/templates/',
  ),
)
Stack Trace:
- vendor/cakephp/cakephp/src/View/View.php:782
- vendor/cakephp/cakephp/src/Controller/Controller.php:705
- src/Controller/PagesController.php:80
- vendor/cakephp/cakephp/src/Controller/Controller.php:498
- vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:139
- vendor/cakephp/cakephp/src/Controller/ControllerFactory.php:114
- vendor/cakephp/cakephp/src/Http/BaseApplication.php:332
- vendor/cakephp/cakephp/src/Http/Runner.php:86
- vendor/cakephp/cakephp/src/Http/Middleware/HttpsEnforcerMiddleware.php:95
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/SecurityHeadersMiddleware.php:258
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/authorization/src/Middleware/AuthorizationMiddleware.php:116
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/authentication/src/Middleware/AuthenticationMiddleware.php:87
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/CsrfProtectionMiddleware.php:159
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Middleware/BodyParserMiddleware.php:157
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Routing/Middleware/RoutingMiddleware.php:118
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Routing/Middleware/AssetMiddleware.php:69
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php:115
- vendor/cakephp/cakephp/src/Http/Runner.php:82
- vendor/cakephp/cakephp/src/Http/Runner.php:67
- vendor/cakephp/cakephp/src/Http/Server.php:103
- vendor/cakephp/cakephp/src/TestSuite/MiddlewareDispatcher.php:141
- vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:499
- vendor/cakephp/cakephp/src/TestSuite/IntegrationTestTrait.php:385
- tests/TestCase/Controller/PagesControllerTest.php:110
- vendor/phpunit/phpunit/src/Framework/TestCase.php:1160
- vendor/phpunit/phpunit/src/Framework/TestCase.php:659
- vendor/phpunit/phpunit/src/Framework/TestRunner.php:105
- vendor/phpunit/phpunit/src/Framework/TestCase.php:493
- vendor/phpunit/phpunit/src/Framework/TestSuite.php:349
- vendor/phpunit/phpunit/src/TextUI/TestRunner.php:62
- vendor/phpunit/phpunit/src/TextUI/Application.php:198
- vendor/phpunit/phpunit/phpunit:104
- vendor/bin/phpunit:122
- [main]:

Request URL: /pages/not_existing

@fr3nch13
Copy link
Contributor Author

Here is the src/Controller/PagesController.php:

<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link      https://cakephp.org CakePHP(tm) Project
 * @since     0.2.9
 * @license   https://opensource.org/licenses/mit-license.php MIT License
 */
namespace App\Controller;

use Cake\Core\Configure;
use Cake\Event\EventInterface;
use Cake\Http\Exception\ForbiddenException;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Response;
use Cake\View\Exception\MissingTemplateException;

/**
 * Static content controller
 *
 * This controller will render views from templates/Pages/
 *
 * @link https://book.cakephp.org/4/en/controllers/pages-controller.html
 */
class PagesController extends AppController
{
    /**
     * Runs before the code in the actions
     *
     * @return void
     */
    public function beforeFilter(EventInterface $event): void
    {
        $this->Authentication->addUnauthenticatedActions(['display']);

        parent::beforeFilter($event);
    }

    /**
     * Displays a view
     *
     * @param string ...$path Path segments.
     * @return \Cake\Http\Response|null
     * @throws \Cake\Http\Exception\ForbiddenException When a directory traversal attempt.
     * @throws \Cake\View\Exception\MissingTemplateException When the view file could not
     *   be found and in debug mode.
     * @throws \Cake\Http\Exception\NotFoundException When the view file could not
     *   be found and not in debug mode.
     * @throws \Cake\View\Exception\MissingTemplateException In debug mode.
     */
    public function display(string ...$path): ?Response
    {
        $activeUser = $this->getActiveUser();

        if (!$path) {
            return $this->redirect('/');
        }
        if (in_array('..', $path, true) || in_array('.', $path, true)) {
            throw new ForbiddenException();
        }
        $page = $subpage = null;

        if (!empty($path[0])) {
            $page = $path[0];
        }
        if (!empty($path[1])) {
            $subpage = $path[1];
        }
        $this->set(compact('page', 'subpage', 'activeUser'));

        try {
            return $this->render(implode('/', $path));
        } catch (MissingTemplateException $exception) {
            if (Configure::read('debug')) {
                throw $exception;
            }
            throw new NotFoundException();
        }
    }
}

@fr3nch13
Copy link
Contributor Author

fr3nch13 commented Apr 15, 2024

I'm wondering if it could be due to how I'm using the new Policy stuff?
I'm using them for general controller policies, instead of the old way of controlling authorization for controllers.

// src/Policy/AppControllerPolicy.php
<?php
declare(strict_types=1);

namespace App\Policy;

use App\Controller\AppController;
use Authorization\IdentityInterface;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\ResultInterface;
use Cake\Core\Configure;
use Cake\Http\Exception\NotFoundException;

/**
 * Base Controller policy
 */
class AppControllerPolicy implements BeforePolicyInterface
{
    /**
     * Summary of before
     *
     * @param ?\Authorization\IdentityInterface $identity The user or null
     * @param mixed $resource The controller
     * @param string $action The name of the action trying to be accessed
     * @return \Authorization\Policy\ResultInterface|bool|null
     * @throws \Cake\Http\Exception\NotFoundException if the action doesn't exist.
     */
    public function before(?IdentityInterface $identity, mixed $resource, string $action): ResultInterface|bool|null
    {
        // throw a 404 here for all missing actions.
        if (!empty($action) && $resource instanceof AppController) {
            if (!method_exists($resource, $action)) {
                $message = __('Page Not Found');
                if (Configure::read('debug')) {
                    $message = __('Missing Action `{0}::{1}()`', [
                        get_class($resource),
                        $action,
                    ]);
                }

                throw new NotFoundException($message);
            }
        }

        // fall through
        // always return null so that the other, more specific policy checks can happen.
        return null;
    }
}
// src/Policy/PagesControllerPolicy.php
<?php
declare(strict_types=1);

namespace App\Policy;

use App\Controller\PagesController;
use App\Model\Entity\User;

/**
 * Pages Controller policy
 */
class PagesControllerPolicy extends AppControllerPolicy
{
    /**
     * Anyone can view pages.
     *
     * @param \App\Model\Entity\User|null $user The identity object.
     * @param \App\Controller\PagesController $PagesController
     * @return bool
     */
    public function canDisplay(?User $user, PagesController $PagesController): bool
    {
        return true;
    }
}

@fr3nch13
Copy link
Contributor Author

Here is my src/Application.php showing how I'm mapping the policies.

<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link      https://cakephp.org CakePHP(tm) Project
 * @since     3.3.0
 * @license   https://opensource.org/licenses/mit-license.php MIT License
 */
namespace App;

use App\Controller\Admin\QrCodesController as AdminQrCodesController;
use App\Controller\Admin\QrImagesController as AdminQrImagesController;
use App\Controller\Admin\SourcesController as AdminSourcesController;
use App\Controller\Admin\TagsController as AdminTagsController;
use App\Controller\Admin\UsersController as AdminUsersController;
use App\Controller\PagesController;
use App\Controller\QrCodesController;
use App\Controller\QrImagesController;
use App\Controller\TagsController;
use App\Controller\UsersController;
use App\Event\QrCodeListener;
use App\Policy\Admin\QrCodesControllerPolicy as AdminQrCodesControllerPolicy;
use App\Policy\Admin\QrImagesControllerPolicy as AdminQrImagesControllerPolicy;
use App\Policy\Admin\SourcesControllerPolicy as AdminSourcesControllerPolicy;
use App\Policy\Admin\TagsControllerPolicy as AdminTagsControllerPolicy;
use App\Policy\Admin\UsersControllerPolicy as AdminUsersControllerPolicy;
use App\Policy\PagesControllerPolicy;
use App\Policy\QrCodesControllerPolicy;
use App\Policy\QrImagesControllerPolicy;
use App\Policy\TagsControllerPolicy;
use App\Policy\UsersControllerPolicy;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Identifier\AbstractIdentifier;
use Authentication\Middleware\AuthenticationMiddleware;
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Exception\ForbiddenException;
use Authorization\Exception\MissingIdentityException;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\MapResolver;
use Authorization\Policy\OrmResolver;
use Authorization\Policy\ResolverCollection;
use Cake\Core\Configure;
use Cake\Core\ContainerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\Middleware\BodyParserMiddleware;
use Cake\Http\Middleware\CsrfProtectionMiddleware;
use Cake\Http\Middleware\HttpsEnforcerMiddleware;
use Cake\Http\Middleware\SecurityHeadersMiddleware;
use Cake\Http\MiddlewareQueue;
use Cake\ORM\Locator\TableLocator;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Cake\Routing\Router;
use Psr\Http\Message\ServerRequestInterface;

/**
 * Application setup class.
 *
 * This defines the bootstrapping logic and middleware layers you
 * want to use in your application.
 *
 * @extends \Cake\Http\BaseApplication<\App\Application>
 */
class Application extends BaseApplication implements
    AuthenticationServiceProviderInterface,
    AuthorizationServiceProviderInterface
{
    /**
     * Load all the application configuration and bootstrap logic.
     *
     * @return void
     */
    public function bootstrap(): void
    {
        // Call parent to load bootstrap from files.
        parent::bootstrap();

        if (PHP_SAPI === 'cli') {
            $this->bootstrapCli();
        }
        FactoryLocator::add(
            'Table',
            (new TableLocator())->allowFallbackClass(false)
        );

        /*
         * Only try to load DebugKit in development mode
         * Debug Kit should not be installed on a production system
         */
        if (Configure::read('debug') && !$this->getPlugins()->has('DebugKit')) {
            $this->addOptionalPlugin('DebugKit');
        }

        // Load more plugins here
        if (!$this->getPlugins()->has('Authentication')) {
            $this->addPlugin('Authentication');
        }

        // CakePHP's Authorization Plugin.
        if (!$this->getPlugins()->has('Authorization')) {
            $this->addPlugin('Authorization');
        }

        // the friendsofcake/bootstrapui plugin.
        if (!$this->getPlugins()->has('BootstrapUI')) {
            $this->addPlugin('BootstrapUI');
        }

        // the friendsofcake/bootstrapui plugin.
        if (!$this->getPlugins()->has('Search')) {
            $this->addPlugin('Search');
        }

        // my stats plugin
        if (!$this->getPlugins()->has('Fr3nch13/Stats')) {
            $this->addPlugin('Fr3nch13/Stats');
        }

        // register the event listeners.
        $this->registerEventListeners();
    }

    /**
     * Setup the middleware queue your application will use.
     *
     * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
     * @return \Cake\Http\MiddlewareQueue The updated middleware queue.
     */
    public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
    {
        $middlewareQueue
            // Catch any exceptions in the lower layers,
            // and make an error page/response
            ->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this))

            // Handle plugin/theme assets like CakePHP normally does.
            ->add(new AssetMiddleware([
                'cacheTime' => Configure::read('Asset.cacheTime'),
            ]))

            // Add routing middleware.
            // If you have a large number of routes connected, turning on routes
            // caching in production could improve performance.
            // See https://github.com/CakeDC/cakephp-cached-routing
            ->add(new RoutingMiddleware($this))

            // Parse various types of encoded request bodies so that they are
            // available as array through $request->getData()
            // https://book.cakephp.org/4/en/controllers/middleware.html#body-parser-middleware
            ->add(new BodyParserMiddleware())

            // Cross Site Request Forgery (CSRF) Protection Middleware
            // https://book.cakephp.org/4/en/security/csrf.html#cross-site-request-forgery-csrf-middleware
            ->add(new CsrfProtectionMiddleware([
                'httponly' => true,
            ]))

            // @link https://book.cakephp.org/5/en/tutorials-and-examples/cms/authentication.html
            ->add(new AuthenticationMiddleware($this))

            // @link https://book.cakephp.org/5/en/tutorials-and-examples/cms/authorization.html
            ->add(new AuthorizationMiddleware($this, [
                'requireAuthorizationCheck' => false,
                'identityDecorator' => function ($auth, $user) {
                    return $user->setAuthorization($auth); //turns the user entity directly into the identity object.
                },
                'unauthorizedHandler' => [
                    'className' => 'CustomRedirect',
                    'url' => Router::url('/admin', true),
                    'queryParam' => 'redirect',
                    'exceptions' => [
                        MissingIdentityException::class,
                        ForbiddenException::class,
                    ],
                    'custom_param' => true,
                ],
            ]));

            /*
            // TODO: Enable when whatever unsafe-inline is fixed.
            // It's causing inline onclicks to be blocked, even though unsafe-inline is set to true.

            // Content Security Policy
            // @link https://book.cakephp.org/5/en/security/content-security-policy.html#content-security-policy-middleware
            // @link https://github.com/paragonie/csp-builder
            ->add(new CspMiddleware([
                'script-src' => [
                    'self' => true,
                    'unsafe-inline' => true,
                    'unsafe-eval' => false,
                    'allow' => [
                        // external domains that can load/run javascript.
                        //'https://www.google-analytics.com',
                    ],
                ],
            ], [
                'scriptNonce' => true,
                'styleNonce' => true,
            ]))
            */

        // @link https://book.cakephp.org/5/en/security/security-headers.html
        $securityHeaders = new SecurityHeadersMiddleware();
        $securityHeaders
            ->setReferrerPolicy()
            ->setXFrameOptions()
            ->noOpen()
            ->noSniff();
        $middlewareQueue->add($securityHeaders);

        $https = new HttpsEnforcerMiddleware([
            'redirect' => true,
            'statusCode' => 302,
            'disableOnDebug' => true,
            'hsts' => [
                // How long the header value should be cached for.
                'maxAge' => 60 * 60 * 24 * 365,
                // should this policy apply to subdomains?
                'includeSubDomains' => true,
                // Should the header value be cacheable in google's HSTS preload
                // service? While not part of the spec it is widely implemented.
                'preload' => true,
            ],
        ]);
        $middlewareQueue->add($https);

        return $middlewareQueue;
    }

    /**
     * Register application container services.
     *
     * @param \Cake\Core\ContainerInterface $container The Container to update.
     * @return void
     * @link https://book.cakephp.org/4/en/development/dependency-injection.html#dependency-injection
     */
    public function services(ContainerInterface $container): void
    {
    }

    /**
     * Gets and Configures the Authentication Service
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     * @return \Authentication\AuthenticationServiceInterface
     */
    public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
    {
        $fields = [
            AbstractIdentifier::CREDENTIAL_USERNAME => 'email',
            AbstractIdentifier::CREDENTIAL_PASSWORD => 'password',
        ];

        $authenticationService = new AuthenticationService([
            'unauthenticatedRedirect' => Router::url([
                'prefix' => false,
                'plugin' => false,
                'controller' => 'Users',
                'action' => 'login',
            ]),
            'queryParam' => 'redirect',
        ]);

        // Load identifiers, ensure we check email and password fields
        $authenticationService->loadIdentifier('Authentication.Password', [
            'fields' => $fields,
            'resolver' => [
                'className' => 'Authentication.Orm',
                'userModel' => 'Users',
                'finder' => 'active',
            ],
        ]);

        // Load the authenticators, you want session first
        $authenticationService->loadAuthenticator('Authentication.Session');

        // Configure form data check to pick email and password
        $authenticationService->loadAuthenticator('Authentication.Form', [
            'fields' => $fields,
            'loginUrl' => [
                Router::url([
                    'prefix' => false,
                    'plugin' => false,
                    'controller' => 'Users',
                    'action' => 'login',
                    '_ext' => null,
                ]),
                Router::url([
                    'prefix' => false,
                    'plugin' => false,
                    'controller' => 'Users',
                    'action' => 'login',
                    '_ext' => 'json',
                ]),
            ],
        ]);

        // Used for Remember me
        $authenticationService->loadAuthenticator('Authentication.Cookie', [
            'fields' => $fields,
            'loginUrl' => [
                Router::url([
                    'prefix' => false,
                    'plugin' => false,
                    'controller' => 'Users',
                    'action' => 'login',
                    '_ext' => null,
                ]),
                Router::url([
                    'prefix' => false,
                    'plugin' => false,
                    'controller' => 'Users',
                    'action' => 'login',
                    '_ext' => 'json',
                ]),
            ],
        ]);

        return $authenticationService;
    }

    /**
     * Gets the Authorization Service
     *
     * @param \Psr\Http\Message\ServerRequestInterface $request
     * @return \Authorization\AuthorizationServiceInterface
     */
    public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
    {
        // Use the Map Resolver to map controllers to a policy.
        $mapResolver = new MapResolver();

        // map the controllers
        $mapResolver->map(QrCodesController::class, QrCodesControllerPolicy::class);
        $mapResolver->map(QrImagesController::class, QrImagesControllerPolicy::class);
        $mapResolver->map(TagsController::class, TagsControllerPolicy::class);
        $mapResolver->map(UsersController::class, UsersControllerPolicy::class);
        $mapResolver->map(PagesController::class, PagesControllerPolicy::class);

        // admin controllers
        $mapResolver->map(AdminQrCodesController::class, AdminQrCodesControllerPolicy::class);
        $mapResolver->map(AdminQrImagesController::class, AdminQrImagesControllerPolicy::class);
        $mapResolver->map(AdminSourcesController::class, AdminSourcesControllerPolicy::class);
        $mapResolver->map(AdminTagsController::class, AdminTagsControllerPolicy::class);
        $mapResolver->map(AdminUsersController::class, AdminUsersControllerPolicy::class);

        $ormResolver = new OrmResolver();

        // @link https://book.cakephp.org/authorization/3/en/policy-resolvers.html#using-multiple-resolvers
        $resolver = new ResolverCollection([
            // make sure this one is first.
            // we want to check the general controller actions,
            $mapResolver,
            // before we check the individual entities,
            // or scopes for index pages.
            $ormResolver,
        ]);

        return new AuthorizationService($resolver);
    }

    /**
     * Register event listeners globally.
     *
     * Called in self::bootstrap()
     *
     * @return void
     */
    protected function registerEventListeners(): void
    {
        /** @var \Cake\Event\EventManager $eventManager */
        $eventManager = $this->getEventManager();
        // make sure they're only getting registered globally, once.
        // TODO: Hacky as we're tracking the event key, not if the listener itself is already registered.
        // Maybe use listeners('QrCode.onHit')
        if (empty($eventManager->prioritisedListeners('QrCode.onHit'))) {
            $eventManager->on(new QrCodeListener());
        }
    }

    /**
     * Bootstrapping for CLI application.
     *
     * That is when running commands.
     *
     * @return void
     */
    protected function bootstrapCli(): void
    {
        if (!$this->getPlugins()->has('Bake')) {
            $this->addOptionalPlugin('Bake');
        }

        if (!$this->getPlugins()->has('Migrations')) {
            $this->addPlugin('Migrations');
        }

        // Load more plugins here
    }
}

@markstory
Copy link
Member

Have you tried not loading the authentication/authorizations plugin to see if that resolves the problem?

Here is the stack traces from the log file if it helps:

Curious that the stack traces are different, and that in debug mode you get to view rendering, while with debug off it is the controller action that is raising an exception.

@fr3nch13
Copy link
Contributor Author

Have you tried not loading the authentication/authorizations plugin to see if that resolves the problem?

I haven't, but I will.

Here is the stack traces from the log file if it helps:

Curious that the stack traces are different, and that in debug mode you get to view rendering, while with debug off it is the controller action that is raising an exception.

Probably because of right here in the src/Controller/PagesController.php

    /**
     * Displays a view
     *
     * @param string ...$path Path segments.
     * @return \Cake\Http\Response|null
     * @throws \Cake\Http\Exception\ForbiddenException When a directory traversal attempt.
     * @throws \Cake\View\Exception\MissingTemplateException When the view file could not
     *   be found and in debug mode.
     * @throws \Cake\Http\Exception\NotFoundException When the view file could not
     *   be found and not in debug mode.
     * @throws \Cake\View\Exception\MissingTemplateException In debug mode.
     */
    public function display(string ...$path): ?Response
    {
        $activeUser = $this->getActiveUser();

        if (!$path) {
            return $this->redirect('/');
        }
        if (in_array('..', $path, true) || in_array('.', $path, true)) {
            throw new ForbiddenException();
        }
        $page = $subpage = null;

        if (!empty($path[0])) {
            $page = $path[0];
        }
        if (!empty($path[1])) {
            $subpage = $path[1];
        }
        $this->set(compact('page', 'subpage', 'activeUser'));

        try {
            return $this->render(implode('/', $path));
        } catch (MissingTemplateException $exception) {
            if (Configure::read('debug')) {
                throw $exception;                                       <---- HERE With Debug on, a 500
            }
            throw new NotFoundException();                 <---- AND HERE a 404, but uses error500.php instead of error400.php
        }
    }

@fr3nch13
Copy link
Contributor Author

Weird. I disable Authentication and Authorization and these specific tests pass:

// tests/TestCase/Controller/PagesControllerTest.php



    /**
     * Test that missing template renders 404 page in production
     *
     * @return void
     */
    public function testMissingTemplate()
    {
        Configure::write('debug', false);
        $this->get('https://localhost/pages/not_existing');
        $this->assertResponseCode(404);
        $this->helperTestError400('/pages/not_existing');    <--- Testing for error400.php , and passing.
    }

    /**
     * Test that missing template in debug mode renders missing_template error page
     *
     * @return void
     */
    public function testMissingTemplateInDebug()
    {
        Configure::write('debug', true);

        $this->get('https://localhost/pages/not_existing');
        $this->assertResponseCode(500);

        $this->assertResponseContains('Missing Template');
        $this->assertResponseContains('stack-frames');
        $this->assertResponseContains('not_existing.php');
    }

I wonder why with Auths integrated, why is would change NotFoundException above to use the error500.php.
And I still can't seem to find the change between 5.0.4 and 5.0.5 that would cause this.

@fr3nch13
Copy link
Contributor Author

fr3nch13 commented Apr 16, 2024

Screw it, I'm just going to make this project public, since it's a personal site I created to track QR codes:

https://github.com/fr3nch13/qr.fr3nch.com

It's a new app, that started on CakePHP 5 as a way to learn the new changes to 5.x. And also give me a way to make and track QR codes I've printed in the wild.

Its code coverage is over 97%.

@fr3nch13
Copy link
Contributor Author

So, maybe I found it, but I still don't know what change between 5.0.4 and 5.0.5 would've caused it.
I ran bin/cake server, then added $e = new Exception(); print_r(str_replace(ROOT, '', $e->getTraceAsString())); to the error500.php and error400.php templates, and it gave me this (truncated path for readability):


Version 5.0.5

#0 /vendor/cakephp/cakephp/src/View/View.php(1188): include()
#1 /vendor/cakephp/cakephp/src/View/View.php(1145): Cake\View\View->_evaluate('/Users/fr3nch13...', Array)
#2 /vendor/cakephp/cakephp/src/View/View.php(785): Cake\View\View->_render('/Users/fr3nch13...')
#3 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(458): Cake\View\View->render('error500', 'error')
--> #4 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(420): Cake\Error\Renderer\WebExceptionRenderer->_outputMessageSafe('error500')
--> #5 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(423): Cake\Error\Renderer\WebExceptionRenderer->_outputMessage('error500')
--> #6 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(282): Cake\Error\Renderer\WebExceptionRenderer->_outputMessage('error400')
--> #7 /vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php(149): Cake\Error\Renderer\WebExceptionRenderer->render()
#8 /vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php(119): Cake\Error\Middleware\ErrorHandlerMiddleware->handleException(Object(Cake\Http\Exception\NotFoundException), Object(Cake\Http\ServerRequest))
#9 /vendor/cakephp/cakephp/src/Http/Runner.php(82): Cake\Error\Middleware\ErrorHandlerMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner))
#10 /vendor/cakephp/cakephp/src/Http/Runner.php(67): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest)) 
#11 /vendor/cakephp/cakephp/src/Http/Server.php(103): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(App\Application)) 
#12 /webroot/index.php(37): Cake\Http\Server->run() #13 {main}
Version 5.0.4

#0 /vendor/cakephp/cakephp/src/View/View.php(1188): include() 
#1 /vendor/cakephp/cakephp/src/View/View.php(1145): Cake\View\View->_evaluate('/Users/fr3nch13...', Array) 
#2 /vendor/cakephp/cakephp/src/View/View.php(785): Cake\View\View->_render('/Users/fr3nch13...') 
#3 /vendor/cakephp/cakephp/src/Controller/Controller.php(705): Cake\View\View->render() 
--> #4 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(412): Cake\Controller\Controller->render('error400') 
--> #5 /vendor/cakephp/cakephp/src/Error/Renderer/WebExceptionRenderer.php(283): Cake\Error\Renderer\WebExceptionRenderer->_outputMessage('error400') 
--> #6 /vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php(150): Cake\Error\Renderer\WebExceptionRenderer->render() 
#7 /vendor/cakephp/cakephp/src/Error/Middleware/ErrorHandlerMiddleware.php(119): Cake\Error\Middleware\ErrorHandlerMiddleware->handleException(Object(Cake\Http\Exception\NotFoundException), Object(Cake\Http\ServerRequest)) 
#8 /vendor/cakephp/cakephp/src/Http/Runner.php(82): Cake\Error\Middleware\ErrorHandlerMiddleware->process(Object(Cake\Http\ServerRequest), Object(Cake\Http\Runner)) 
#9 /vendor/cakephp/cakephp/src/Http/Runner.php(67): Cake\Http\Runner->handle(Object(Cake\Http\ServerRequest)) 
#10 /vendor/cakephp/cakephp/src/Http/Server.php(103): Cake\Http\Runner->run(Object(Cake\Http\MiddlewareQueue), Object(Cake\Http\ServerRequest), Object(App\Application)) 
#11 /webroot/index.php(37): Cake\Http\Server->run() #12 {main}

But that method hasn't changed.
https://github.com/cakephp/cakephp/blob/5.0.4/src/Error/Renderer/WebExceptionRenderer.php#L424
https://github.com/cakephp/cakephp/blob/5.0.5/src/Error/Renderer/WebExceptionRenderer.php#L423

It's called by the ErrorHandlerMiddleware and there is a change in it from 5.0.4 to 5.0.5 at
5.0.4...5.0.5#diff-aff0c9a62d0ee25e99d5331845f5bceddbad2223546c1c4fdc97f4f85feb7359R149

It all seems to come down to this chunk right here:
In 5.0.4 it renders as expected, and returns $this->_shutdown();
https://github.com/cakephp/cakephp/blob/5.0.4/src/Error/Renderer/WebExceptionRenderer.php#L414
In >=5.0.5 the MissingTemplateException is thrown
https://github.com/cakephp/cakephp/blob/5.0.5/src/Error/Renderer/WebExceptionRenderer.php#L423

@fr3nch13
Copy link
Contributor Author

One interesting thing...

At https://github.com/cakephp/cakephp/blob/5.0.4/src/Error/Renderer/WebExceptionRenderer.php#L412
If I add:

        try {
            print_r(get_class($this->controller));
            print_r($template);
            $this->controller->render($template);

            return $this->_shutdown();
        } catch (MissingTemplateException $e) {

I get

App\Controller\ErrorController
error400

but in https://github.com/cakephp/cakephp/blob/5.0.5/src/Error/Renderer/WebExceptionRenderer.php#L411
If I add the same, I get:

Cake\Controller\Controller
error400
Cake\Controller\Controller
error500

Here is my ErrorController.php https://github.com/fr3nch13/qr.fr3nch.com/blob/main/src/Controller/ErrorController.php
which only has

<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         3.3.4
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace App\Controller;

use Cake\Event\EventInterface;

/**
 * Error Handling Controller
 *
 * Controller used by ExceptionRenderer to render error responses.
 */
class ErrorController extends AppController
{
    /**
     * beforeRender callback.
     *
     * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event.
     * @return void
     */
    public function beforeRender(EventInterface $event): void
    {
        parent::beforeRender($event);

        $this->viewBuilder()->setTemplatePath('Error'); <--- Here
    }
}

@markstory markstory modified the milestones: 5.0.8, 5.0.9 May 11, 2024
@markstory
Copy link
Member

class ErrorController extends AppController

This could be the problem, do you have any components like Authorization or Authentication? Both of these plugins can raise exceptions during exception rendering which counts as a 500 to the framework.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants