Issue #2920001 by Wim Leers, dawehner, borisson_, aheimlich, davidwbarratt: Add cacheable HTTP exceptions: Symfony HTTP exceptions + Drupal cacheability metadata
parent
52a598e2bf
commit
b83782a74b
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Cache;
|
||||
|
||||
/**
|
||||
* Trait for \Drupal\Core\Cache\CacheableDependencyInterface.
|
||||
*/
|
||||
trait CacheableDependencyTrait {
|
||||
|
||||
/**
|
||||
* Cache contexts.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheContexts = [];
|
||||
|
||||
/**
|
||||
* Cache tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheTags = [];
|
||||
|
||||
/**
|
||||
* Cache max-age.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cacheMaxAge = Cache::PERMANENT;
|
||||
|
||||
/**
|
||||
* Sets cacheability; useful for value object constructors.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheability
|
||||
* The cacheability to set.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setCacheability(CacheableDependencyInterface $cacheability) {
|
||||
$this->cacheContexts = $cacheability->getCacheContexts();
|
||||
$this->cacheTags = $cacheability->getCacheTags();
|
||||
$this->cacheMaxAge = $cacheability->getCacheMaxAge();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7,47 +7,7 @@ namespace Drupal\Core\Cache;
|
|||
*/
|
||||
trait RefinableCacheableDependencyTrait {
|
||||
|
||||
/**
|
||||
* Cache contexts.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheContexts = [];
|
||||
|
||||
/**
|
||||
* Cache tags.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $cacheTags = [];
|
||||
|
||||
/**
|
||||
* Cache max-age.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cacheMaxAge = Cache::PERMANENT;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return $this->cacheTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheContexts() {
|
||||
return $this->cacheContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheMaxAge() {
|
||||
return $this->cacheMaxAge;
|
||||
}
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheableResponseInterface;
|
||||
use Drupal\Core\Routing\RedirectDestinationInterface;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -170,6 +171,13 @@ class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
|
|||
$response->setStatusCode($status_code);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Persist any special HTTP headers that were set on the exception.
|
||||
if ($exception instanceof HttpExceptionInterface) {
|
||||
$response->headers->add($exception->getHeaders());
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableJsonResponse;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
|
||||
|
||||
|
|
@ -35,7 +37,16 @@ class ExceptionJsonSubscriber extends HttpExceptionSubscriberBase {
|
|||
public function on4xx(GetResponseForExceptionEvent $event) {
|
||||
/** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */
|
||||
$exception = $event->getException();
|
||||
$response = new JsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode(), $exception->getHeaders());
|
||||
|
||||
// If the exception is cacheable, generate a cacheable response.
|
||||
if ($exception instanceof CacheableDependencyInterface) {
|
||||
$response = new CacheableJsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode(), $exception->getHeaders());
|
||||
$response->addCacheableDependency($exception);
|
||||
}
|
||||
else {
|
||||
$response = new JsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode(), $exception->getHeaders());
|
||||
}
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable AccessDeniedHttpException.
|
||||
*/
|
||||
class CacheableAccessDeniedHttpException extends AccessDeniedHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable BadRequestHttpException.
|
||||
*/
|
||||
class CacheableBadRequestHttpException extends BadRequestHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable ConflictHttpException.
|
||||
*/
|
||||
class CacheableConflictHttpException extends ConflictHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable GoneHttpException.
|
||||
*/
|
||||
class CacheableGoneHttpException extends GoneHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
/**
|
||||
* A cacheable HttpException.
|
||||
*/
|
||||
class CacheableHttpException extends HttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $statusCode = 0, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($statusCode, $message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\LengthRequiredHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable LengthRequiredHttpException.
|
||||
*/
|
||||
class CacheableLengthRequiredHttpException extends LengthRequiredHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable MethodNotAllowedHttpException.
|
||||
*/
|
||||
class CacheableMethodNotAllowedHttpException extends MethodNotAllowedHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, array $allow, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($allow, $message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable NotAcceptableHttpException.
|
||||
*/
|
||||
class CacheableNotAcceptableHttpException extends NotAcceptableHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable NotFoundHttpException.
|
||||
*/
|
||||
class CacheableNotFoundHttpException extends NotFoundHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable PreconditionFailedHttpException.
|
||||
*/
|
||||
class CacheablePreconditionFailedHttpException extends PreconditionFailedHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\PreconditionRequiredHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable PreconditionRequiredHttpException.
|
||||
*/
|
||||
class CacheablePreconditionRequiredHttpException extends PreconditionRequiredHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable ServiceUnavailableHttpException.
|
||||
*/
|
||||
class CacheableServiceUnavailableHttpException extends ServiceUnavailableHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $retryAfter = NULL, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($retryAfter, $message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable TooManyRequestsHttpException.
|
||||
*/
|
||||
class CacheableTooManyRequestsHttpException extends TooManyRequestsHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $retryAfter = NULL, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($retryAfter, $message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable UnauthorizedHttpException.
|
||||
*/
|
||||
class CacheableUnauthorizedHttpException extends UnauthorizedHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $challenge, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($challenge, $message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable UnprocessableEntityHttpException.
|
||||
*/
|
||||
class CacheableUnprocessableEntityHttpException extends UnprocessableEntityHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Core\Http\Exception;
|
||||
|
||||
use Drupal\Core\Cache\CacheableDependencyInterface;
|
||||
use Drupal\Core\Cache\CacheableDependencyTrait;
|
||||
use Symfony\Component\HttpKernel\Exception\UnsupportedMediaTypeHttpException;
|
||||
|
||||
/**
|
||||
* A cacheable UnsupportedMediaTypeHttpException.
|
||||
*/
|
||||
class CacheableUnsupportedMediaTypeHttpException extends UnsupportedMediaTypeHttpException implements CacheableDependencyInterface {
|
||||
|
||||
use CacheableDependencyTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(CacheableDependencyInterface $cacheability, $message = NULL, \Exception $previous = NULL, $code = 0) {
|
||||
$this->setCacheability($cacheability);
|
||||
parent::__construct($message, $previous, $code);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,12 +5,13 @@ namespace Drupal\basic_auth\Authentication\Provider;
|
|||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Authentication\AuthenticationProviderInterface;
|
||||
use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Flood\FloodInterface;
|
||||
use Drupal\Core\Http\Exception\CacheableUnauthorizedHttpException;
|
||||
use Drupal\user\UserAuthInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
|
||||
/**
|
||||
* HTTP Basic authentication provider.
|
||||
|
|
@ -126,11 +127,35 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function challengeException(Request $request, \Exception $previous) {
|
||||
$site_name = $this->configFactory->get('system.site')->get('name');
|
||||
$site_config = $this->configFactory->get('system.site');
|
||||
$site_name = $site_config->get('name');
|
||||
$challenge = SafeMarkup::format('Basic realm="@realm"', [
|
||||
'@realm' => !empty($site_name) ? $site_name : 'Access restricted',
|
||||
]);
|
||||
return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
|
||||
|
||||
// A 403 is converted to a 401 here, but it doesn't matter what the
|
||||
// cacheability was of the 403 exception: what matters here is that
|
||||
// authentication credentials are missing, i.e. that this request was made
|
||||
// as the anonymous user.
|
||||
// Therefore, all we must do, is make this response:
|
||||
// 1. vary by whether the current user has the 'anonymous' role or not. This
|
||||
// works fine because:
|
||||
// - Thanks to \Drupal\basic_auth\PageCache\DisallowBasicAuthRequests,
|
||||
// Page Cache never caches a response whose request has Basic Auth
|
||||
// credentials.
|
||||
// - Dynamic Page Cache will cache a different result for when the
|
||||
// request is unauthenticated (this 401) versus authenticated (some
|
||||
// other response)
|
||||
// 2. have the 'config:user.role.anonymous' cache tag, because the only
|
||||
// reason this 401 would no longer be a 401 is if permissions for the
|
||||
// 'anonymous' role change, causing that cache tag to be invalidated.
|
||||
// @see \Drupal\Core\EventSubscriber\AuthenticationSubscriber::onExceptionSendChallenge()
|
||||
// @see \Drupal\Core\EventSubscriber\ClientErrorResponseSubscriber()
|
||||
// @see \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onAllResponds()
|
||||
$cacheability = CacheableMetadata::createFromObject($site_config)
|
||||
->addCacheTags(['config:user.role.anonymous'])
|
||||
->addCacheContexts(['user.roles:anonymous']);
|
||||
return new CacheableUnauthorizedHttpException($cacheability, (string) $challenge, 'No authentication credentials provided.', $previous);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use Drupal\Core\Url;
|
|||
use Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests for BasicAuth authentication provider.
|
||||
|
|
@ -180,6 +181,47 @@ class BasicAuthTest extends BrowserTestBase {
|
|||
$this->assertText('Access denied', "A user friendly access denied message is displayed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the cacheability of Basic Auth's 401 response.
|
||||
*
|
||||
* @see \Drupal\basic_auth\Authentication\Provider\BasicAuth::challengeException()
|
||||
*/
|
||||
public function testCacheabilityOf401Response() {
|
||||
$session = $this->getSession();
|
||||
$url = Url::fromRoute('router_test.11');
|
||||
|
||||
$assert_response_cacheability = function ($expected_page_cache_header_value, $expected_dynamic_page_cache_header_value) use ($session, $url) {
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(401);
|
||||
$this->assertSame($expected_page_cache_header_value, $session->getResponseHeader('X-Drupal-Cache'));
|
||||
$this->assertSame($expected_dynamic_page_cache_header_value, $session->getResponseHeader('X-Drupal-Dynamic-Cache'));
|
||||
};
|
||||
|
||||
// 1. First request: cold caches, both Page Cache and Dynamic Page Cache are
|
||||
// now primed.
|
||||
$assert_response_cacheability('MISS', 'MISS');
|
||||
// 2. Second request: Page Cache HIT, we don't even hit Dynamic Page Cache.
|
||||
// This is going to keep happening.
|
||||
$assert_response_cacheability('HIT', 'MISS');
|
||||
// 3. Third request: after clearing Page Cache, we now see that Dynamic Page
|
||||
// Cache is a HIT too.
|
||||
$this->container->get('cache.page')->deleteAll();
|
||||
$assert_response_cacheability('MISS', 'HIT');
|
||||
// 4. Fourth request: warm caches.
|
||||
$assert_response_cacheability('HIT', 'HIT');
|
||||
|
||||
// If the permissions of the 'anonymous' role change, it may no longer be
|
||||
// necessary to be authenticated to access this route. Therefore the cached
|
||||
// 401 responses should be invalidated.
|
||||
$this->grantPermissions(Role::load(Role::ANONYMOUS_ID), [$this->randomMachineName()]);
|
||||
$assert_response_cacheability('MISS', 'MISS');
|
||||
$assert_response_cacheability('HIT', 'MISS');
|
||||
// Idem for when the 'system.site' config changes.
|
||||
$this->config('system.site')->save();
|
||||
$assert_response_cacheability('MISS', 'MISS');
|
||||
$assert_response_cacheability('HIT', 'MISS');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the controller is called before authentication.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,3 +7,8 @@ services:
|
|||
class: Drupal\rest_test\Authentication\Provider\TestAuthGlobal
|
||||
tags:
|
||||
- { name: authentication_provider, provider_id: 'rest_test_auth_global', global: TRUE }
|
||||
rest_test.page_cache_request_policy.deny_test_auth_requests:
|
||||
class: Drupal\rest_test\PageCache\RequestPolicy\DenyTestAuthRequests
|
||||
public: false
|
||||
tags:
|
||||
- { name: page_cache_request_policy }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\rest_test\PageCache\RequestPolicy;
|
||||
|
||||
use Drupal\Core\PageCache\RequestPolicyInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class DenyTestAuthRequests implements RequestPolicyInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function check(Request $request) {
|
||||
if ($request->headers->has('REST-test-auth') || $request->headers->has('REST-test-auth-global')) {
|
||||
return self::DENY;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -414,14 +414,20 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
':pattern' => '%[route]=rest.%',
|
||||
])
|
||||
->fetchAllAssoc('cid');
|
||||
$this->assertCount(2, $cache_items);
|
||||
$this->assertTrue(count($cache_items) >= 2);
|
||||
$found_cache_redirect = FALSE;
|
||||
$found_cached_response = FALSE;
|
||||
$found_cached_200_response = FALSE;
|
||||
$other_cached_responses_are_4xx = TRUE;
|
||||
foreach ($cache_items as $cid => $cache_item) {
|
||||
$cached_data = unserialize($cache_item->data);
|
||||
if (!isset($cached_data['#cache_redirect'])) {
|
||||
$found_cached_response = TRUE;
|
||||
$cached_response = $cached_data['#response'];
|
||||
if ($cached_response->getStatusCode() === 200) {
|
||||
$found_cached_200_response = TRUE;
|
||||
}
|
||||
elseif (!$cached_response->isClientError()) {
|
||||
$other_cached_responses_are_4xx = FALSE;
|
||||
}
|
||||
$this->assertNotInstanceOf(ResourceResponseInterface::class, $cached_response);
|
||||
$this->assertInstanceOf(CacheableResponseInterface::class, $cached_response);
|
||||
}
|
||||
|
|
@ -430,7 +436,8 @@ abstract class EntityResourceTestBase extends ResourceTestBase {
|
|||
}
|
||||
}
|
||||
$this->assertTrue($found_cache_redirect);
|
||||
$this->assertTrue($found_cached_response);
|
||||
$this->assertTrue($found_cached_200_response);
|
||||
$this->assertTrue($other_cached_responses_are_4xx);
|
||||
}
|
||||
$cache_tags_header_value = $response->getHeader('X-Drupal-Cache-Tags')[0];
|
||||
$this->assertEquals($this->getExpectedCacheTags(), empty($cache_tags_header_value) ? [] : explode(' ', $cache_tags_header_value));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue