diff --git a/composer.json b/composer.json index 7e647d5..ac5d6fa 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "phpunit/phpunit": "4.1.*", "phpunit/phpunit-mock-objects": "dev-master#e60bb929c50ae4237aaf680a4f6773f4ee17f0a2", "zendframework/zend-feed": "2.2.*", - "mikey179/vfsStream": "1.*" + "mikey179/vfsStream": "1.*", + "stack/builder": "1.0.*" }, "autoload": { "psr-4": { diff --git a/core/core.services.yml b/core/core.services.yml index b015208..2dc8eb4 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -370,7 +370,29 @@ services: arguments: ['@controller_resolver', '@string_translation'] http_kernel: class: Symfony\Component\HttpKernel\HttpKernel + factory_method: resolve + factory_service: http_kernel_factory + arguments: ['@http_kernel.basic'] + http_kernel_factory: + class: Stack\Builder + http_kernel.basic: + class: Symfony\Component\HttpKernel\HttpKernel arguments: ['@event_dispatcher', '@controller_resolver', '@request_stack'] + http_middleware.reverse_proxy: + class: Drupal\Core\StackMiddleware\ReverseProxyMiddleware + arguments: ['@settings'] + tags: + - { name: http_middleware, priority: 300 } + http_middleware.page_cache: + class: Drupal\Core\StackMiddleware\PageCache + arguments: ['@kernel'] + tags: + - { name: http_middleware, priority: 200 } + http_middleware.kernel_pre_handle: + class: Drupal\Core\StackMiddleware\KernelPreHandle + arguments: ['@kernel'] + tags: + - { name: http_middleware, priority: 100 } language_manager: class: Drupal\Core\Language\LanguageManager arguments: ['@language.default'] @@ -559,11 +581,6 @@ services: tags: - { name: event_subscriber } arguments: ['@resolver_manager.entity'] - reverse_proxy_subscriber: - class: Drupal\Core\EventSubscriber\ReverseProxySubscriber - tags: - - { name: event_subscriber } - arguments: ['@settings'] ajax_subscriber: class: Drupal\Core\EventSubscriber\AjaxSubscriber tags: diff --git a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php index 9580fa9..7c357c9 100644 --- a/core/lib/Drupal/Core/Authentication/AuthenticationManager.php +++ b/core/lib/Drupal/Core/Authentication/AuthenticationManager.php @@ -183,7 +183,7 @@ public function handleException(GetResponseForExceptionEvent $event) { $active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId()); // Get the sorted list of active providers for the given route. - $providers = array_intersect($active_providers, array_keys($this->providers)); + $providers = array_intersect($active_providers, array_keys($this->getSortedProviders())); foreach ($providers as $provider_id) { if ($this->providers[$provider_id]->handleException($event) == TRUE) { diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 8662d64..8393903 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -10,6 +10,7 @@ use Drupal\Core\Cache\CacheContextsPass; use Drupal\Core\Cache\ListCacheBinsPass; use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass; +use Drupal\Core\DependencyInjection\Compiler\StackedKernelPass; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass; @@ -50,6 +51,8 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new BackendCompilerPass()); + $container->addCompilerPass(new StackedKernelPass()); + // Collect tagged handler services as method calls on consumer services. $container->addCompilerPass(new TaggedHandlersPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedKernelPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedKernelPass.php new file mode 100644 index 0000000..bf8efc0 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/StackedKernelPass.php @@ -0,0 +1,45 @@ +hasDefinition('http_kernel_factory')) { + return; + } + + $http_kernel_factory = $container->getDefinition('http_kernel_factory'); + $middleware_priorities = array(); + $middleware_arguments = array(); + foreach ($container->findTaggedServiceIds('http_middleware') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $middleware_priorities[$id] = $priority; + $definition = $container->getDefinition($id); + $middleware_arguments[$id] = $definition->getArguments(); + array_unshift($middleware_arguments[$id], $definition->getClass()); + } + array_multisort($middleware_priorities, SORT_DESC, $middleware_arguments, SORT_DESC); + + foreach ($middleware_arguments as $id => $push_arguments) { + $http_kernel_factory->addMethodCall('push', $push_arguments); + } + } + +} diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index da657c5..e950ff1 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -410,12 +410,9 @@ public function getContainer() { } /** - * Helper method that does request related initialization. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The current request. + * {@inheritdoc} */ - protected function preHandle(Request $request) { + public function preHandle(Request $request) { // Load all enabled modules. $this->container->get('module_handler')->loadAll(); @@ -560,7 +557,6 @@ public function terminate(Request $request, Response $response) { */ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { $this->boot(); - $this->preHandle($request); return $this->getHttpKernel()->handle($request, $type, $catch); } diff --git a/core/lib/Drupal/Core/DrupalKernelInterface.php b/core/lib/Drupal/Core/DrupalKernelInterface.php index 08d007c..ca7af2b 100644 --- a/core/lib/Drupal/Core/DrupalKernelInterface.php +++ b/core/lib/Drupal/Core/DrupalKernelInterface.php @@ -108,4 +108,12 @@ public function handlePageCache(Request $request); */ public function prepareLegacyRequest(Request $request); + /** + * Helper method that does request related initialization. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + */ + public function preHandle(Request $request); + } diff --git a/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php deleted file mode 100644 index 788076a..0000000 --- a/core/lib/Drupal/Core/EventSubscriber/ReverseProxySubscriber.php +++ /dev/null @@ -1,63 +0,0 @@ -settings = $settings; - } - - /** - * Passes reverse proxy settings to current request. - * - * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event - * The Event to process. - */ - public function onKernelRequestReverseProxyCheck(GetResponseEvent $event) { - $request = $event->getRequest(); - if ($this->settings->get('reverse_proxy', 0)) { - $reverse_proxy_header = $this->settings->get('reverse_proxy_header', 'HTTP_X_FORWARDED_FOR'); - $request::setTrustedHeaderName($request::HEADER_CLIENT_IP, $reverse_proxy_header); - $reverse_proxy_addresses = $this->settings->get('reverse_proxy_addresses', array()); - $request::setTrustedProxies($reverse_proxy_addresses); - } - } - - /** - * Registers the methods in this class that should be listeners. - * - * @return array - * An array of event listener definitions. - */ - static function getSubscribedEvents() { - $events[KernelEvents::REQUEST][] = array('onKernelRequestReverseProxyCheck', 10); - return $events; - } -} diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index b1e2260..3417549 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -22,7 +22,6 @@ use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\FilterResponseEvent; -use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\KernelEvents; @@ -127,10 +126,10 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS * The theme manager. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token generator. - * @param \Symfony\Component\HttpKernel\HttpKernel $http_kernel + * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel * The HTTP kernel. */ - public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernel $http_kernel = NULL) { + public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL, HttpKernelInterface $http_kernel = NULL) { $this->formValidator = $form_validator; $this->formSubmitter = $form_submitter; $this->formCache = $form_cache; diff --git a/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php b/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php new file mode 100644 index 0000000..1ccdea1 --- /dev/null +++ b/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php @@ -0,0 +1,57 @@ +httpKernel = $http_kernel; + $this->drupalKernel = $drupal_kernel; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + $this->drupalKernel->preHandle($request); + + return $this->httpKernel->handle($request, $type, $catch); + } + +} + diff --git a/core/lib/Drupal/Core/StackMiddleware/PageCache.php b/core/lib/Drupal/Core/StackMiddleware/PageCache.php new file mode 100644 index 0000000..ba59e93 --- /dev/null +++ b/core/lib/Drupal/Core/StackMiddleware/PageCache.php @@ -0,0 +1,55 @@ +httpKernel = $http_kernel; + $this->drupalKernel = $drupal_kernel; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + $this->drupalKernel->handlePageCache($request); + + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php b/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php new file mode 100644 index 0000000..344364d --- /dev/null +++ b/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php @@ -0,0 +1,61 @@ +httpKernel = $http_kernel; + $this->settings = $settings; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + // Initialize proxy settings. + if ($this->settings->get('reverse_proxy', FALSE)) { + $reverse_proxy_header = $this->settings->get('reverse_proxy_header', 'X_FORWARDED_FOR'); + $request::setTrustedHeaderName($request::HEADER_CLIENT_IP, $reverse_proxy_header); + $proxies = $this->settings->get('reverse_proxy_addresses', array()); + if (count($proxies) > 0) { + $request::setTrustedProxies($proxies); + } + } + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/core/modules/system/src/Tests/HttpKernel/StackKernelIntegrationTest.php b/core/modules/system/src/Tests/HttpKernel/StackKernelIntegrationTest.php new file mode 100644 index 0000000..ee37f7e --- /dev/null +++ b/core/modules/system/src/Tests/HttpKernel/StackKernelIntegrationTest.php @@ -0,0 +1,50 @@ +installSchema('system', 'router'); + } + + /** + * Tests a request. + */ + public function testRequest() { + $request = new Request(); + /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */ + $http_kernel = \Drupal::service('http_kernel'); + $http_kernel->handle($request); + + $this->assertEqual($request->attributes->get('_hello'), 'world'); + $this->assertEqual($request->attributes->get('_previous_optional_argument'), 'test_argument'); + } + +} + diff --git a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml new file mode 100644 index 0000000..5f1375f --- /dev/null +++ b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.info.yml @@ -0,0 +1,6 @@ +name: 'HttpKernel test' +type: module +description: 'Support module for httpkernel tests.' +package: Testing +version: VERSION +core: 8.x diff --git a/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml new file mode 100644 index 0000000..c4784de --- /dev/null +++ b/core/modules/system/tests/modules/httpkernel_test/httpkernel_test.services.yml @@ -0,0 +1,10 @@ +services: + httpkernel_test.test_middleware: + class: Drupal\httpkernel_test\HttpKernel\TestMiddleware + tags: + - { name: http_middleware } + httpkernel_test.test_middleware2: + class: Drupal\httpkernel_test\HttpKernel\TestMiddleware + arguments: ['test_argument'] + tags: + - { name: http_middleware, priority: 20 } diff --git a/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php new file mode 100644 index 0000000..93e1a89 --- /dev/null +++ b/core/modules/system/tests/modules/httpkernel_test/src/HttpKernel/TestMiddleware.php @@ -0,0 +1,60 @@ +kernel = $kernel; + $this->optionalArgument = $optional_argument; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) { + $request->attributes->set('_hello', 'world'); + if ($request->attributes->has('_optional_argument')) { + $request->attributes->set('_previous_optional_argument', $request->attributes->get('_optional_argument')); + } + elseif (isset($this->optionalArgument)) { + $request->attributes->set('_optional_argument', $this->optionalArgument); + } + + return $this->kernel->handle($request, $type, $catch); + } + +} diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/ReverseProxySubscriberUnitTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/ReverseProxySubscriberUnitTest.php deleted file mode 100644 index 2b19788..0000000 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/ReverseProxySubscriberUnitTest.php +++ /dev/null @@ -1,103 +0,0 @@ -assertEquals(0, $settings->get('reverse_proxy')); - - $subscriber = new ReverseProxySubscriber($settings); - // Mock a request object. - $request = $this->getMock('Symfony\Component\HttpFoundation\Request', array('setTrustedHeaderName', 'setTrustedProxies')); - // setTrustedHeaderName() should never fire. - $request->expects($this->never()) - ->method('setTrustedHeaderName'); - // Mock a response event. - $event = $this->getMockedEvent($request); - // Actually call the check method. - $subscriber->onKernelRequestReverseProxyCheck($event); - } - - /** - * Tests that subscriber sets trusted headers when reverse proxy is set. - */ - public function testReverseProxyEnabled() { - $cases = array( - array( - 'reverse_proxy_header' => 'HTTP_X_FORWARDED_FOR', - 'reverse_proxy_addresses' => array(), - ), - array( - 'reverse_proxy_header' => 'X_FORWARDED_HOST', - 'reverse_proxy_addresses' => array('127.0.0.2', '127.0.0.3'), - ), - ); - foreach ($cases as $case) { - // Enable reverse proxy and add test values. - $settings = new Settings(array('reverse_proxy' => 1) + $case); - $this->trustedHeadersAreSet($settings); - } - } - - /** - * Tests that trusted header methods are called. - * - * \Symfony\Component\HttpFoundation\Request::setTrustedHeaderName() and - * \Symfony\Component\HttpFoundation\Request::setTrustedProxies() should - * always be called when reverse proxy settings are enabled. - * - * @param \Drupal\Core\Site\Settings $settings - * The settings object that holds reverse proxy configuration. - */ - protected function trustedHeadersAreSet(Settings $settings) { - $subscriber = new ReverseProxySubscriber($settings); - $request = new Request(); - - $event = $this->getMockedEvent($request); - $subscriber->onKernelRequestReverseProxyCheck($event); - $this->assertSame($settings->get('reverse_proxy_header'), $request->getTrustedHeaderName($request::HEADER_CLIENT_IP)); - $this->assertSame($settings->get('reverse_proxy_addresses'), $request->getTrustedProxies()); - } - - /** - * Creates a mocked event. - * - * Mocks a \Symfony\Component\HttpKernel\Event\GetResponseEvent object - * and stubs its getRequest() method to return a mocked request object. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * A mocked Request object. - * - * @return \Symfony\Component\HttpKernel\Event\GetResponseEvent - * The GetResponseEvent mocked object. - */ - protected function getMockedEvent($request) { - $event = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseEvent') - ->disableOriginalConstructor() - ->getMock(); - $event->expects($this->once()) - ->method('getRequest') - ->will($this->returnValue($request)); - return $event; - } -} diff --git a/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php new file mode 100644 index 0000000..231f833 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/StackMiddleware/ReverseProxyMiddlewareTest.php @@ -0,0 +1,100 @@ +mockHttpKernel = $this->getMock('Symfony\Component\HttpKernel\HttpKernelInterface'); + } + + /** + * Tests that subscriber does not act when reverse proxy is not set. + */ + public function testNoProxy() { + $settings = new Settings(array()); + $this->assertEquals(0, $settings->get('reverse_proxy')); + + $middleware = new ReverseProxyMiddleware($this->mockHttpKernel, $settings); + // Mock a request object. + $request = $this->getMock('Symfony\Component\HttpFoundation\Request', array('setTrustedHeaderName', 'setTrustedProxies')); + // setTrustedHeaderName() should never fire. + $request->expects($this->never()) + ->method('setTrustedHeaderName'); + // Actually call the check method. + $middleware->handle($request); + } + + /** + * Tests that subscriber sets trusted headers when reverse proxy is set. + * + * @dataProvider testReverseProxyEnabledProvider + */ + public function testReverseProxyEnabled($provided_settings) { + // Enable reverse proxy and add test values. + $settings = new Settings(array('reverse_proxy' => 1) + $provided_settings); + $this->trustedHeadersAreSet($settings); + } + + /** + * Data provider for testReverseProxyEnabled. + */ + public function testReverseProxyEnabledProvider() { + return array( + array( + array( + 'reverse_proxy_header' => 'HTTP_X_FORWARDED_FOR', + 'reverse_proxy_addresses' => array(), + ), + ), + array( + array( + 'reverse_proxy_header' => 'X_FORWARDED_HOST', + 'reverse_proxy_addresses' => array('127.0.0.2', '127.0.0.3'), + ), + ), + ); + } + + /** + * Tests that trusted header methods are called. + * + * \Symfony\Component\HttpFoundation\Request::setTrustedHeaderName() and + * \Symfony\Component\HttpFoundation\Request::setTrustedProxies() should + * always be called when reverse proxy settings are enabled. + * + * @param \Drupal\Core\Site\Settings $settings + * The settings object that holds reverse proxy configuration. + */ + protected function trustedHeadersAreSet(Settings $settings) { + $middleware = new ReverseProxyMiddleware($this->mockHttpKernel, $settings); + $request = new Request(); + + $middleware->handle($request); + $this->assertSame($settings->get('reverse_proxy_header'), $request->getTrustedHeaderName($request::HEADER_CLIENT_IP)); + $this->assertSame($settings->get('reverse_proxy_addresses'), $request->getTrustedProxies()); + } +} diff --git a/index.php b/index.php index 78ee18c..406d3dc 100644 --- a/index.php +++ b/index.php @@ -19,8 +19,7 @@ $request = Request::createFromGlobals(); $kernel = DrupalKernel::createFromRequest($request, $autoloader, 'prod'); $response = $kernel - ->handlePageCache($request) - ->handle($request) + ->handle($request) // Handle the response object. ->prepare($request)->send(); $kernel->terminate($request, $response);