.../DefaultExceptionHtmlSubscriber.php | 6 ++++-- .../CacheableMethodNotAllowedHttpException.php | 4 ++-- .../RequestPolicy/DenyTestAuthRequests.php | 5 +++++ .../ExceptionJsonSubscriberTest.php | 25 ++++++++++++++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php index 98a46a1..59e95dd 100644 --- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @@ -172,8 +172,10 @@ protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $st $response->setStatusCode($status_code); } - // Persist the exception's cacheability metadata, if any. - if ($response instanceof CacheableResponseInterface && $exception instanceof CacheableDependencyInterface) { + // Persist the exception's cacheability metadata, if any. If the exception + // itself isn't cacheable, then this will make the response uncacheable: + // max-age=0 will be set. + if ($response instanceof CacheableResponseInterface) { $response->addCacheableDependency($exception); } diff --git a/core/lib/Drupal/Core/Http/Exception/CacheableMethodNotAllowedHttpException.php b/core/lib/Drupal/Core/Http/Exception/CacheableMethodNotAllowedHttpException.php index 1ccc388..d9919b1 100644 --- a/core/lib/Drupal/Core/Http/Exception/CacheableMethodNotAllowedHttpException.php +++ b/core/lib/Drupal/Core/Http/Exception/CacheableMethodNotAllowedHttpException.php @@ -16,9 +16,9 @@ class CacheableMethodNotAllowedHttpException extends MethodNotAllowedHttpExcepti /** * {@inheritdoc} */ - public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) { + public function __construct(CacheableDependencyInterface $cacheability, array $allow, $message = NULL, \Exception $previous = NULL, $code = 0) { $this->setCacheability($cacheability); - parent::__construct($message, $previous, $code); + parent::__construct($allow, $message, $previous, $code); } } diff --git a/core/modules/rest/tests/modules/rest_test/src/PageCache/RequestPolicy/DenyTestAuthRequests.php b/core/modules/rest/tests/modules/rest_test/src/PageCache/RequestPolicy/DenyTestAuthRequests.php index 8b547a9..17be647 100644 --- a/core/modules/rest/tests/modules/rest_test/src/PageCache/RequestPolicy/DenyTestAuthRequests.php +++ b/core/modules/rest/tests/modules/rest_test/src/PageCache/RequestPolicy/DenyTestAuthRequests.php @@ -8,6 +8,11 @@ /** * Cache policy for pages requested with REST Test Auth. * + * This policy disallows caching of requests that use the REST Test Auth + * authentication provider for security reasons (just like basic_auth). + * Otherwise responses for authenticated requests can get into the page cache + * and could be delivered to unprivileged users. + * * @see \Drupal\rest_test\Authentication\Provider\TestAuth * @see \Drupal\rest_test\Authentication\Provider\TestAuthGlobal * @see \Drupal\basic_auth\PageCache\DisallowBasicAuthRequests diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php index 86c6d86..8f339a8 100644 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php @@ -2,11 +2,15 @@ namespace Drupal\Tests\Core\EventSubscriber; +use Drupal\Core\Cache\CacheableJsonResponse; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\EventSubscriber\ExceptionJsonSubscriber; +use Drupal\Core\Http\Exception\CacheableMethodNotAllowedHttpException; use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -18,21 +22,34 @@ class ExceptionJsonSubscriberTest extends UnitTestCase { /** * @covers ::on4xx + * @dataProvider providerTestOn4xx */ - public function testOn4xx() { + public function testOn4xx(HttpExceptionInterface $exception, $expected_response_class) { $kernel = $this->prophesize(HttpKernelInterface::class); $request = Request::create('/test'); - $e = new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message'); - $event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $e); + $event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $exception); $subscriber = new ExceptionJsonSubscriber(); $subscriber->on4xx($event); $response = $event->getResponse(); - $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertInstanceOf($expected_response_class, $response); $this->assertEquals('{"message":"test message"}', $response->getContent()); $this->assertEquals(405, $response->getStatusCode()); $this->assertEquals('POST, PUT', $response->headers->get('Allow')); $this->assertEquals('application/json', $response->headers->get('Content-Type')); } + public function providerTestOn4xx() { + return [ + 'uncacheable exception' => [ + new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message'), + JsonResponse::class + ], + 'cacheable exception' => [ + new CacheableMethodNotAllowedHttpException((new CacheableMetadata())->setCacheContexts(['route']), ['POST', 'PUT'], 'test message'), + CacheableJsonResponse::class + ], + ]; + } + }