diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php index d95873d..1f55ebd 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php @@ -9,6 +9,7 @@ use Drupal\Core\Routing\RouteMatch; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; /** * Manager for authentication. @@ -84,12 +85,26 @@ public function appliesToRoutedRequest(Request $request, $authenticated) { /** * {@inheritdoc} */ - public function challengeException(Request $request, \Exception $previous) { - $provider_id = $this->getChallenger($request); + public function challenge(Request $request, \Exception $previous) { + $providers = $this->getChallengers($request); + + if (!empty($providers)) { + $challenges = []; + foreach ($providers as $provider_id) { + $provider = $this->authCollector->getProvider($provider_id); + $challenge = $provider->challenge($request, $previous); + if (!empty($challenge)) { + if ($challenge instanceof \Exception) { + return $challenge; + } + + $challenges[] = $challenge; + } + } - if ($provider_id) { - $provider = $this->authCollector->getProvider($provider_id); - return $provider->challengeException($request, $previous); + if (!empty($challenges)) { + return new UnauthorizedHttpException(implode(', ', $challenges), 'No authentication credentials provided', $previous); + } } } @@ -117,16 +132,20 @@ protected function getProvider(Request $request) { * @param \Symfony\Component\HttpFoundation\Request $request * The incoming request. * - * @return string|NULL - * The ID of the first authentication provider which applies to the request. - * If no application detects appropriate credentials, then NULL is returned. + * @return array(string) + * The IDs of the authentication providers which applies to the request. + * If no application detects appropriate credentials, then am empty array is + * returned. */ - protected function getChallenger(Request $request) { + protected function getChallengers(Request $request) { + $providers = []; foreach ($this->authCollector->getSortedProviders() as $provider_id => $provider) { if (($provider instanceof AuthenticationProviderChallengeInterface) && !$provider->applies($request) && $this->applyFilter($request, FALSE, $provider_id)) { - return $provider_id; + $providers[] = $provider_id; } } + + return $providers; } /** diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php index 1a83947..df66ab0 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationProviderChallengeInterface.php @@ -26,9 +26,12 @@ * @var \Exception $exception * The previous exception. * - * @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|NULL + * @return \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|string|NULL * An exception to be used in order to generate an authentication challenge. + * If a string is passed, it is appended to challenges headers for + * UnauthorizedHttpException, otherwise if an instance of \Exception is + * passed it is returned as is. */ - public function challengeException(Request $request, \Exception $previous); + public function challenge(Request $request, \Exception $previous); } diff --git a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php index 6fd0b99..a8a57d7 100644 --- a/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/AuthenticationSubscriber.php @@ -118,8 +118,8 @@ public function onExceptionSendChallenge(GetResponseForExceptionEvent $event) { $request = $event->getRequest(); $exception = $event->getException(); if ($exception instanceof AccessDeniedHttpException && !$this->authenticationProvider->applies($request) && (!isset($this->filter) || $this->filter->appliesToRoutedRequest($request, FALSE))) { - $challenge_exception = $this->challengeProvider->challengeException($request, $exception); - if ($challenge_exception) { + $challenge_exception = $this->challengeProvider->challenge($request, $exception); + if ($challenge_exception instanceof \Exception) { $event->setException($challenge_exception); } } diff --git a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php index 641ac89..c2edb25 100644 --- a/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php +++ b/core/modules/basic_auth/src/Authentication/Provider/BasicAuth.php @@ -130,12 +130,12 @@ public function authenticate(Request $request) { /** * {@inheritdoc} */ - public function challengeException(Request $request, \Exception $previous) { + public function challenge(Request $request, \Exception $previous) { $site_name = $this->configFactory->get('system.site')->get('name'); $challenge = SafeMarkup::format('Basic realm="@realm"', array( '@realm' => !empty($site_name) ? $site_name : 'Access restricted', )); - return new UnauthorizedHttpException($challenge, 'No authentication credentials provided.', $previous); + return $challenge; } } diff --git a/core/modules/system/src/Tests/Authentication/AuthenticationProviderHeaderTest.php b/core/modules/system/src/Tests/Authentication/AuthenticationProviderHeaderTest.php new file mode 100644 index 0000000..59081f3 --- /dev/null +++ b/core/modules/system/src/Tests/Authentication/AuthenticationProviderHeaderTest.php @@ -0,0 +1,38 @@ +drupalGet($url); + $header = $this->drupalGetHeader('www-authenticate'); + $header = array_map('trim', explode(',', $header)); + $this->assertTrue(in_array('Test', $header), 'Drupal should return the Test Auth header'); + $this->assertTrue(in_array(SafeMarkup::format('Basic realm="@realm"', ['@realm' => \Drupal::config('system.site')->get('name')]), $header), 'Drupal should return the basic auth header'); + $this->assertResponse('401', 'Not authenticated on the route that allows only basic_auth. Prompt to authenticate received.'); + } + +} \ No newline at end of file diff --git a/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.info.yml b/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.info.yml new file mode 100644 index 0000000..437b76f --- /dev/null +++ b/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.info.yml @@ -0,0 +1,6 @@ +name: 'Auth provider test' +type: module +description: 'Support module for testing authentication.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.services.yml b/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.services.yml new file mode 100644 index 0000000..421e668 --- /dev/null +++ b/core/modules/system/tests/modules/auth_provider_test/auth_provider_test.services.yml @@ -0,0 +1,5 @@ +services: + auth_provider_test.authentication.test_auth: + class: Drupal\auth_provider_test\Authentication\Provider\TestAuthProvider + tags: + - { name: authentication_provider, provider_id: 'test_auth', priority: 100 } diff --git a/core/modules/system/tests/modules/auth_provider_test/src/Authentication/Provider/TestAuthProvider.php b/core/modules/system/tests/modules/auth_provider_test/src/Authentication/Provider/TestAuthProvider.php new file mode 100644 index 0000000..562daf0 --- /dev/null +++ b/core/modules/system/tests/modules/auth_provider_test/src/Authentication/Provider/TestAuthProvider.php @@ -0,0 +1,41 @@ +