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.
* {@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);
}
}