core/lib/Drupal/Core/Access/AccessResult.php | 17 +++++++++++++++-- core/lib/Drupal/Core/Access/AccessResultForbidden.php | 18 ++++++++++++++++++ core/lib/Drupal/Core/Routing/AccessAwareRouter.php | 3 ++- core/modules/rest/src/Access/CSRFAccessCheck.php | 2 +- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Access/AccessResult.php b/core/lib/Drupal/Core/Access/AccessResult.php index 8148d4c..3ece571 100644 --- a/core/lib/Drupal/Core/Access/AccessResult.php +++ b/core/lib/Drupal/Core/Access/AccessResult.php @@ -51,11 +51,16 @@ public static function allowed() { /** * Creates an AccessResultInterface object with isForbidden() === TRUE. * + * @param string|null $reason + * (optional) The reason why access is forbidden. Intended for developers, + * hence not translatable. + * * @return \Drupal\Core\Access\AccessResult * isForbidden() will be TRUE. */ - public static function forbidden() { - return new AccessResultForbidden(); + public static function forbidden($reason = NULL) { + assert('is_string($reason) || is_null($reason)'); + return new AccessResultForbidden($reason); } /** @@ -334,8 +339,16 @@ public function andIf(AccessResultInterface $other) { if ($this->isForbidden() || $other->isForbidden()) { $result = static::forbidden(); if (!$this->isForbidden()) { + if ($other instanceof AccessResultForbidden) { + $result->setReason($other->getReason()); + } $merge_other = TRUE; } + else { + if ($this instanceof AccessResultForbidden) { + $result->setReason($this->getReason()); + } + } } elseif ($this->isAllowed() && $other->isAllowed()) { $result = static::allowed(); diff --git a/core/lib/Drupal/Core/Access/AccessResultForbidden.php b/core/lib/Drupal/Core/Access/AccessResultForbidden.php index 4dc0120..85b5639 100644 --- a/core/lib/Drupal/Core/Access/AccessResultForbidden.php +++ b/core/lib/Drupal/Core/Access/AccessResultForbidden.php @@ -8,10 +8,28 @@ class AccessResultForbidden extends AccessResult { /** + * The reason why access is forbidden. For use in error messages. + * + * @var string|null + */ + protected $reason; + + /** * {@inheritdoc} */ public function isForbidden() { return TRUE; } + // @todo Create interface. + public function getReason() { + return $this->reason; + } + + // @todo Create interface. + public function setReason($reason) { + $this->reason = $reason; + return $this; + } + } diff --git a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php index 80e16eb..1f11987 100644 --- a/core/lib/Drupal/Core/Routing/AccessAwareRouter.php +++ b/core/lib/Drupal/Core/Routing/AccessAwareRouter.php @@ -3,6 +3,7 @@ namespace Drupal\Core\Routing; use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Access\AccessResultForbidden; use Drupal\Core\Session\AccountInterface; use Symfony\Cmf\Component\Routing\ChainRouter; use Symfony\Component\HttpFoundation\Request; @@ -105,7 +106,7 @@ protected function checkAccess(Request $request) { $request->attributes->set(AccessAwareRouterInterface::ACCESS_RESULT, $access_result); } if (!$access_result->isAllowed()) { - throw new AccessDeniedHttpException(); + throw new AccessDeniedHttpException($access_result instanceof AccessResultForbidden ? $access_result->getReason() : NULL); } } diff --git a/core/modules/rest/src/Access/CSRFAccessCheck.php b/core/modules/rest/src/Access/CSRFAccessCheck.php index c6dc04f..d2f25d6 100644 --- a/core/modules/rest/src/Access/CSRFAccessCheck.php +++ b/core/modules/rest/src/Access/CSRFAccessCheck.php @@ -78,7 +78,7 @@ public function access(Request $request, AccountInterface $account) { ) { $csrf_token = $request->headers->get('X-CSRF-Token'); if (!\Drupal::csrfToken()->validate($csrf_token, 'rest')) { - return AccessResult::forbidden()->setCacheMaxAge(0); + return AccessResult::forbidden()->setReason('X-CSRF-Token request header is missing')->setCacheMaxAge(0); } } // Let other access checkers decide if the request is legit.