Skip to content

Commit

Permalink
Merge pull request #1089 from ezsystems/fix_EZP-23632_reverseUserCont…
Browse files Browse the repository at this point in the history
…extRegression

Fix EZP-23632: Exception when running eZ Publish behind built in reverse proxy
  • Loading branch information
lolautruche committed Nov 14, 2014
2 parents e80729d + 880dc52 commit c8a1b7d
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 122 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@
use eZ\Publish\Core\MVC\Symfony\MVCEvents;
use eZ\Publish\Core\MVC\Symfony\SiteAccess\SiteAccessAware;
use eZ\Publish\Core\MVC\Symfony\View\ViewManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ConfigScopeListener extends ContainerAware implements EventSubscriberInterface
class ConfigScopeListener implements EventSubscriberInterface
{
/**
* @var \eZ\Publish\Core\MVC\Symfony\Configuration\VersatileScopeInterface
Expand All @@ -30,38 +28,20 @@ class ConfigScopeListener extends ContainerAware implements EventSubscriberInter
*/
private $viewManager;

/**
* Array of serviceIds to reset in the container.
*
* @var array
*/
private $resettableServiceIds;

/**
* Array of "fake" services handling dynamic settings injection.
*
* @var array
*/
private $dynamicSettingsServiceIds;

public function __construct(
VersatileScopeInterface $configResolver,
ViewManagerInterface $viewManager,
array $resettableServiceIds,
array $dynamicSettingsServiceIds
ViewManagerInterface $viewManager
)
{
$this->configResolver = $configResolver;
$this->viewManager = $viewManager;
$this->resettableServiceIds = $resettableServiceIds;
$this->dynamicSettingsServiceIds = $dynamicSettingsServiceIds;
}

public static function getSubscribedEvents()
{
return array(
MVCEvents::CONFIG_SCOPE_CHANGE => 'onConfigScopeChange',
MVCEvents::CONFIG_SCOPE_RESTORE => 'onConfigScopeChange',
MVCEvents::CONFIG_SCOPE_CHANGE => array( 'onConfigScopeChange', 100 ),
MVCEvents::CONFIG_SCOPE_RESTORE => array( 'onConfigScopeChange', 100 )
);
}

Expand All @@ -73,19 +53,5 @@ public function onConfigScopeChange( ScopeChangeEvent $event )
{
$this->viewManager->setSiteAccess( $siteAccess );
}

// Ensure to reset services that need to be.
foreach ( $this->resettableServiceIds as $serviceId )
{
$this->container->set( $serviceId, null );
}

// Force dynamic settings services to synchronize.
// This will trigger services depending on dynamic settings to update if they use setter injection.
foreach ( $this->dynamicSettingsServiceIds as $fakeServiceId )
{
$this->container->set( $fakeServiceId, null );
$this->container->set( $fakeServiceId, $this->container->get( $fakeServiceId ) );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
/**
* File containing the DynamicSettingsListener class.
*
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
* @version //autogentag//
*/

namespace eZ\Bundle\EzPublishCoreBundle\EventListener;

use eZ\Publish\Core\MVC\Symfony\Event\PostSiteAccessMatchEvent;
use eZ\Publish\Core\MVC\Symfony\Event\ScopeChangeEvent;
use eZ\Publish\Core\MVC\Symfony\MVCEvents;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class DynamicSettingsListener extends ContainerAware implements EventSubscriberInterface
{
/**
* Array of serviceIds to reset in the container.
*
* @var array
*/
private $resettableServiceIds;

/**
* Array of "fake" services handling dynamic settings injection.
*
* @var array
*/
private $dynamicSettingsServiceIds;

public function __construct( array $resettableServiceIds, array $dynamicSettingsServiceIds )
{
$this->resettableServiceIds = $resettableServiceIds;
$this->dynamicSettingsServiceIds = $dynamicSettingsServiceIds;
}

public static function getSubscribedEvents()
{
return array(
MVCEvents::SITEACCESS => array( 'onSiteAccessMatch', 254 ),
MVCEvents::CONFIG_SCOPE_CHANGE => array( 'onConfigScopeChange', 90 ),
MVCEvents::CONFIG_SCOPE_RESTORE => array( 'onConfigScopeChange', 90 )
);
}

public function onSiteAccessMatch( PostSiteAccessMatchEvent $event )
{
if ( $event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST )
{
return;
}

$this->resetDynamicSettings();
}

public function onConfigScopeChange( ScopeChangeEvent $event )
{
$this->resetDynamicSettings();
}

/**
* Ensure that dynamic settings are correctly reset,
* so that services that rely on those are correctly updated
*/
private function resetDynamicSettings()
{
// Ensure to reset services that need to be.
foreach ( $this->resettableServiceIds as $serviceId )
{
$this->container->set( $serviceId, null );
}

// Force dynamic settings services to synchronize.
// This will trigger services depending on dynamic settings to update if they use setter injection.
foreach ( $this->dynamicSettingsServiceIds as $fakeServiceId )
{
$this->container->set( $fakeServiceId, null );
$this->container->set( $fakeServiceId, $this->container->get( $fakeServiceId ) );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* File containing the OriginalRequestListener class.
*
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
* @version //autogentag//
*/

namespace eZ\Bundle\EzPublishCoreBundle\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;

/**
* Request listener setting potential original request as current request attribute.
* Such situation occurs when generating user context hash from an external reverse proxy (e.g. Varnish).
*/
class OriginalRequestListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array( 'onKernelRequest', 200 )
);
}

public function onKernelRequest( GetResponseEvent $event )
{
if ( $event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST )
{
return;
}

$request = $event->getRequest();
if ( !$request->headers->has( 'x-fos-original-url' ) )
{
return;
}

$originalRequest = Request::create(
$request->getSchemeAndHttpHost() . $request->headers->get( 'x-fos-original-url' ),
'GET', array(), array(), array(),
array( 'HTTP_ACCEPT' => $request->headers->get( 'x-fos-original-accept' ) )
);
$originalRequest->headers->set( 'user-agent', $request->headers->get( 'user-agent' ) );
$originalRequest->headers->set( 'accept-language', $request->headers->get( 'accept-language' ) );
$request->attributes->set( '_ez_original_request', $originalRequest );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,12 @@ class RequestEventListener implements EventSubscriberInterface
*/
private $router;

/**
* @var \eZ\Publish\SPI\HashGenerator
*/
private $hashGenerator;

public function __construct( ConfigResolverInterface $configResolver, RouterInterface $router, $defaultSiteAccess, HashGenerator $hashGenerator, LoggerInterface $logger = null )
public function __construct( ConfigResolverInterface $configResolver, RouterInterface $router, $defaultSiteAccess, LoggerInterface $logger = null )
{
$this->configResolver = $configResolver;
$this->defaultSiteAccess = $defaultSiteAccess;
$this->router = $router;
$this->logger = $logger;
$this->hashGenerator = $hashGenerator;
}

public static function getSubscribedEvents()
Expand Down
7 changes: 7 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/HttpCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,11 @@ protected function getInternalAllowedIPs()
{
return array( '127.0.0.1', '::1', 'fe80::1' );
}

protected function cleanupForwardRequest( Request $forwardReq, Request $originalRequest )
{
parent::cleanupForwardRequest( $forwardReq, $originalRequest );
// Embed the original request as we need it to match the SiteAccess.
$forwardReq->attributes->set( '_ez_original_request', $originalRequest );
}
}
7 changes: 7 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/helpers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ parameters:
ezpublish.field_helper.class: eZ\Publish\Core\Helper\FieldHelper
ezpublish.content_preview_helper.class: eZ\Publish\Core\Helper\ContentPreviewHelper
ezpublish.config_scope_listener.class: eZ\Bundle\EzPublishCoreBundle\EventListener\ConfigScopeListener
ezpublish.dynamic_settings_listener.class: eZ\Bundle\EzPublishCoreBundle\EventListener\DynamicSettingsListener
ezpublish.config_resolver.resettable_services: []
ezpublish.config_resolver.dynamic_settings_services: []

Expand Down Expand Up @@ -32,6 +33,12 @@ services:
arguments:
- @ezpublish.config.resolver.core
- @ezpublish.view_manager
tags:
- { name: kernel.event_subscriber }

ezpublish.dynamic_settings_listener:
class: %ezpublish.dynamic_settings_listener.class%
arguments:
- %ezpublish.config_resolver.resettable_services%
- %ezpublish.config_resolver.dynamic_settings_services%
calls:
Expand Down
7 changes: 6 additions & 1 deletion eZ/Bundle/EzPublishCoreBundle/Resources/config/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ parameters:
ezpublish.siteaccess_match_listener.class: eZ\Publish\Core\MVC\Symfony\EventListener\SiteAccessMatchListener
ezpublish.route_reference.generator.class: eZ\Publish\Core\MVC\Symfony\Routing\Generator\RouteReferenceGenerator
ezpublish.route_reference.listener.language_switch.class: eZ\Publish\Core\MVC\Symfony\EventListener\LanguageSwitchListener
ezpublish.original_request_listener.class: eZ\Bundle\EzPublishCoreBundle\EventListener\OriginalRequestListener

services:
ezpublish.chain_router:
Expand Down Expand Up @@ -92,7 +93,6 @@ services:
- @ezpublish.config.resolver
- @router
- %ezpublish.siteaccess.default%
- @ezpublish.user.hash_generator
- @?logger
tags:
- { name: kernel.event_subscriber }
Expand All @@ -108,3 +108,8 @@ services:
arguments: [@ezpublish.translation_helper]
tags:
- { name: kernel.event_subscriber }

ezpublish.original_request_listener:
class: %ezpublish.original_request_listener.class%
tags:
- { name: kernel.event_subscriber }
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ _ezpublishPreviewContent:
_ezpublishPreviewContentDefaultSa:
path: /content/versionview/{contentId}/{versionNo}/{language}
defaults: { _controller: ezpublish.controller.content.preview:previewContentAction }

_ez_user_hash:
path: /_fos_user_context_hash
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,19 @@ class ConfigScopeListenerTest extends PHPUnit_Framework_TestCase
*/
private $viewManager;

/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
private $container;

protected function setUp()
{
parent::setUp();
$this->configResolver = $this->getMock( 'eZ\Publish\Core\MVC\Symfony\Configuration\VersatileScopeInterface' );
$this->viewManager = $this->getMock( 'eZ\Bundle\EzPublishCoreBundle\Tests\EventListener\Stubs\ViewManager' );
$this->container = $this->getMock( 'Symfony\Component\DependencyInjection\ContainerInterface' );
}

public function testGetSubscribedEvents()
{
$this->assertSame(
array(
MVCEvents::CONFIG_SCOPE_CHANGE => 'onConfigScopeChange',
MVCEvents::CONFIG_SCOPE_RESTORE => 'onConfigScopeChange',
MVCEvents::CONFIG_SCOPE_CHANGE => array( 'onConfigScopeChange', 100 ),
MVCEvents::CONFIG_SCOPE_RESTORE => array( 'onConfigScopeChange', 100 )
),
ConfigScopeListener::getSubscribedEvents()
);
Expand All @@ -55,8 +49,6 @@ public function testOnConfigScopeChange()
{
$siteAccess = new SiteAccess( 'test' );
$event = new ScopeChangeEvent( $siteAccess );
$resettableServices = array( 'foo', 'bar.baz' );
$dynamicSettingsServiceIds = array( 'something', 'something_else' );
$this->configResolver
->expects( $this->once() )
->method( 'setDefaultScope' )
Expand All @@ -65,47 +57,8 @@ public function testOnConfigScopeChange()
->expects( $this->once() )
->method( 'setSiteAccess' )
->with( $siteAccess );
$this->container
->expects( $this->at( 0 ) )
->method( 'set' )
->with( 'foo', null );
$this->container
->expects( $this->at( 1 ) )
->method( 'set' )
->with( 'bar.baz', null );

$fakeService1 = new \stdClass;
$this->container
->expects( $this->at( 2 ) )
->method( 'set' )
->with( 'something', null );
$this->container
->expects( $this->at( 3 ) )
->method( 'get' )
->with( 'something' )
->will( $this->returnValue( $fakeService1 ) );
$this->container
->expects( $this->at( 4 ) )
->method( 'set' )
->with( 'something', $fakeService1 );

$fakeService2 = new \stdClass;
$this->container
->expects( $this->at( 5 ) )
->method( 'set' )
->with( 'something_else', null );
$this->container
->expects( $this->at( 6 ) )
->method( 'get' )
->with( 'something_else' )
->will( $this->returnValue( $fakeService2 ) );
$this->container
->expects( $this->at( 7 ) )
->method( 'set' )
->with( 'something_else', $fakeService2 );

$listener = new ConfigScopeListener( $this->configResolver, $this->viewManager, $resettableServices, $dynamicSettingsServiceIds );
$listener->setContainer( $this->container );
$listener = new ConfigScopeListener( $this->configResolver, $this->viewManager );
$listener->onConfigScopeChange( $event );
$this->assertSame( $siteAccess, $event->getSiteAccess() );
}
Expand Down
Loading

0 comments on commit c8a1b7d

Please sign in to comment.