CVE-2024-27915 (published 2024-03-06T20:15Z)

Sulu is a PHP content management system. Starting in verson 2.2.0 and prior to version 2.4.17 and 2.5.13, access to pages is granted regardless of role permissions for webspaces which have a security system configured and permission check enabled. Webspaces without do not have this issue. The problem is patched in versions 2.4.17 and 2.5.13. Some workarounds are available. One may apply the patch to `vendor/symfony/security-http/HttpUtils.php` manually or avoid installing `symfony/security-http` versions greater equal than `v5.4.30` or `v6.3.6`.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/sulu/sulu
commit_details: repo_url: https://github.com/sulu/sulu
author: Alexander Schranz
author_date: 2024-03-04 13:53:32+01:00
msg: Merge pull request from GHSA-jr83-m233-gg6p

* Fix setting of Webspace Security System before Symfony RouteListener

* Fix Redirect for Security System checker error

* Add test case to avoid regression

* Add test for none secured pages

* Fix some PHP 7.2 issues

* Remmove website based system listener test

* Activate security only for specific test

* Remove old ContentRouteProviderTests

* Fix phpcs

* Skip security listener test for older symfony security system

* Fix code style

* Fix phpstan issues

* Throw deprecation when requestAnalyzer is still given

* Fix code style

* Mark new service as internal and final
repository: repo_url: https://github.com/sulu/sulu
repo_name: sulu/sulu
description: Core framework that implements the functionality of the Sulu content management system
repo_language: PHP
owner: sulu
file_changes: filename: phpstan-baseline.neon
change_type: ModificationType.MODIFY
diff: @@ -29621,12 +29621,12 @@ parameters:
 			path: src/Sulu/Bundle/SecurityBundle/EventListener/SuluSecurityListener.php
 
 		-
-			message: "#^If condition is always true\\.$#"
+			message: "#^Method Sulu\\\\Bundle\\\\SecurityBundle\\\\EventListener\\\\SystemListener\\:\\:onKernelRequest\\(\\) has no return type specified\\.$#"
 			count: 1
 			path: src/Sulu/Bundle/SecurityBundle/EventListener/SystemListener.php
 
 		-
-			message: "#^Method Sulu\\\\Bundle\\\\SecurityBundle\\\\EventListener\\\\SystemListener\\:\\:onKernelRequest\\(\\) has no return type specified\\.$#"
+			message: "#^Property Sulu\\\\Bundle\\\\SecurityBundle\\\\EventListener\\\\SystemListener\\:\\:\\$requestAnalyzer is never read, only written\\.$#"
 			count: 1
 			path: src/Sulu/Bundle/SecurityBundle/EventListener/SystemListener.php
 
@@ -30550,11 +30550,6 @@ parameters:
 			count: 1
 			path: src/Sulu/Bundle/SecurityBundle/Tests/Unit/EventListener/SuluSecurityListenerTest.php
 
-		-
-			message: "#^Method Sulu\\\\Bundle\\\\SecurityBundle\\\\EventListener\\\\SystemListenerTest\\:\\:provideSetWebsiteSystem\\(\\) has no return type specified\\.$#"
-			count: 1
-			path: src/Sulu/Bundle/SecurityBundle/Tests/Unit/EventListener/SystemListenerTest.php
-
 		-
 			message: "#^Class Sulu\\\\Bundle\\\\SecurityBundle\\\\Security\\\\AuthenticationEntryPoint does not have a constructor and must be instantiated without any parameters\\.$#"
 			count: 1
@@ -34920,6 +34915,11 @@ parameters:
 			count: 1
 			path: src/Sulu/Bundle/WebsiteBundle/Routing/ContentRouteProvider.php
 
+		-
+			message: "#^Property Sulu\\\\Bundle\\\\WebsiteBundle\\\\Routing\\\\ContentRouteProvider\\:\\:\\$securityChecker is never read, only written\\.$#"
+			count: 1
+			path: src/Sulu/Bundle/WebsiteBundle/Routing/ContentRouteProvider.php
+
 		-
 			message: "#^Strict comparison using \\=\\=\\= between null and string will always evaluate to false\\.$#"
 			count: 1

programming_language: PHP
filename: request_analyzer.xml
change_type: ModificationType.MODIFY
diff: @@ -51,6 +51,12 @@
             <tag name="sulu.context" context="website"/>
             <tag name="sulu.request_attributes" priority="-64"/>
         </service>
+        <service id="sulu_core.request_processor.system"
+                 class="Sulu\Component\Webspace\Analyzer\Attributes\SystemRequestProcessor">
+            <argument type="service" id="sulu_security.system_store" />
+            <argument>%sulu.context%</argument>
+            <tag name="sulu.request_attributes" priority="-32"/>
+        </service>
         <service id="sulu_core.request_processor.date_time"
                  class="Sulu\Component\Webspace\Analyzer\Attributes\DateTimeRequestProcessor">
             <tag name="sulu.context" context="website"/>

programming_language: TypeScript
filename: SystemListener.php
change_type: ModificationType.MODIFY
diff: @@ -27,7 +27,7 @@ class SystemListener implements EventSubscriberInterface
     private $systemStore;
 
     /**
-     * @var RequestAnalyzerInterface
+     * @var RequestAnalyzerInterface|null
      */
     private $requestAnalyzer;
 
@@ -38,10 +38,15 @@ class SystemListener implements EventSubscriberInterface
 
     public function __construct(
         SystemStoreInterface $systemStore,
-        RequestAnalyzerInterface $requestAnalyzer,
+        ?RequestAnalyzerInterface $requestAnalyzer,
         string $context
     ) {
         $this->systemStore = $systemStore;
+
+        if (null !== $requestAnalyzer) {
+            @trigger_deprecation('sulu/sulu', '2.4', 'The argument "%s" in class "%s" is deprecated and not longer required set `null` instead.', RequestAnalyzerInterface::class, __CLASS__);
+        }
+
         $this->requestAnalyzer = $requestAnalyzer;
         $this->context = $context;
     }
@@ -58,15 +63,5 @@ class SystemListener implements EventSubscriberInterface
 
             return;
         }
-
-        $webspace = $this->requestAnalyzer->getWebspace();
-        if ($webspace) {
-            $security = $webspace->getSecurity();
-            if ($security) {
-                $this->systemStore->setSystem($security->getSystem());
-
-                return;
-            }
-        }
     }
 }

programming_language: PHP
filename: services.xml
change_type: ModificationType.MODIFY
diff: @@ -306,7 +306,7 @@
 
         <service id="sulu_security.system_listener" class="Sulu\Bundle\SecurityBundle\EventListener\SystemListener">
             <argument type="service" id="sulu_security.system_store" />
-            <argument type="service" id="sulu_core.webspace.request_analyzer"/>
+            <argument>null</argument>
             <argument>%sulu.context%</argument>
 
             <tag name="kernel.event_subscriber"/>

programming_language: SQL
filename: SystemListenerTest.php
change_type: ModificationType.MODIFY
diff: @@ -15,8 +15,6 @@ use PHPUnit\Framework\TestCase;
 use Prophecy\Prophecy\ObjectProphecy;
 use Sulu\Bundle\SecurityBundle\System\SystemStoreInterface;
 use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
-use Sulu\Component\Webspace\Security;
-use Sulu\Component\Webspace\Webspace;
 use Symfony\Component\HttpKernel\Event\RequestEvent;
 
 class SystemListenerTest extends TestCase
@@ -46,33 +44,6 @@ class SystemListenerTest extends TestCase
         $this->systemStore->setSystem('Sulu')->shouldBeCalled();
     }
 
-    public function provideSetWebsiteSystem()
-    {
-        return [
-            ['sulu-test'],
-            ['sulu-blog'],
-        ];
-    }
-
-    /**
-     * @dataProvider provideSetWebsiteSystem
-     */
-    public function testSetWebsiteSystem(string $system): void
-    {
-        $systemListener = $this->createSystemListener('website');
-        $requestEvent = $this->prophesize(RequestEvent::class);
-
-        $webspace = new Webspace();
-        $security = new Security();
-        $security->setSystem($system);
-        $webspace->setSecurity($security);
-        $this->requestAnalyzer->getWebspace()->willReturn($webspace);
-
-        $systemListener->onKernelRequest($requestEvent->reveal());
-
-        $this->systemStore->setSystem($system)->shouldBeCalled();
-    }
-
     private function createSystemListener(string $context): SystemListener
     {
         return new SystemListener($this->systemStore->reveal(), $this->requestAnalyzer->reveal(), $context);

programming_language: PHP
filename: RouterListener.php
change_type: ModificationType.MODIFY
diff: @@ -54,6 +54,7 @@ class RouterListener implements EventSubscriberInterface
         // This call is required in all cases, because the default router needs our webspace information
         // Would be nice to also only call this if the _requestAnalyzer attribute is set, but it's set on the next line
         $this->requestAnalyzer->analyze($request);
+
         $this->baseRouteListener->onKernelRequest($event);
         if (false !== $request->attributes->getBoolean(static::REQUEST_ANALYZER, true)) {
             $this->requestAnalyzer->validate($request);

programming_language: PHP
filename: SecurityListener.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,83 @@
+<?php
+
+/*
+ * This file is part of Sulu.
+ *
+ * (c) Sulu GmbH
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Sulu\Bundle\WebsiteBundle\EventListener;
+
+use Sulu\Bundle\PageBundle\Document\BasePageDocument;
+use Sulu\Component\Content\Compat\Structure\PageBridge;
+use Sulu\Component\DocumentManager\Subscriber\EventSubscriberInterface;
+use Sulu\Component\Security\Authorization\PermissionTypes;
+use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
+use Sulu\Component\Security\Authorization\SecurityCondition;
+use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
+use Symfony\Component\HttpKernel\Event\RequestEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+class SecurityListener implements EventSubscriberInterface
+{
+    /**
+     * @var SecurityCheckerInterface|null
+     */
+    private $securityChecker;
+
+    public function __construct(
+        ?SecurityCheckerInterface $securityChecker = null
+    ) {
+        $this->securityChecker = $securityChecker;
+    }
+
+    public static function getSubscribedEvents(): array
+    {
+        return [
+            KernelEvents::REQUEST => [
+                ['onKernelRequest', 7], // set the security listener after the firewall and after the routing listener
+            ],
+        ];
+    }
+
+    public function onKernelRequest(RequestEvent $event): void
+    {
+        $request = $event->getRequest();
+
+        if (null === $this->securityChecker) {
+            return;
+        }
+
+        $requestAttributes = $request->attributes->get('_sulu');
+        if (!$requestAttributes instanceof RequestAttributes) {
+            return;
+        }
+
+        $webspace = $requestAttributes->getAttribute('webspace');
+
+        $structure = $request->attributes->get('structure');
+        if (!$structure instanceof PageBridge) {
+            return;
+        }
+
+        $document = $structure->getDocument();
+        if (!$document instanceof BasePageDocument) {
+            return;
+        }
+
+        if ($webspace->hasWebsiteSecurity()) {
+            $this->securityChecker->checkPermission(
+                new SecurityCondition(
+                    'sulu.webspaces.' . $document->getWebspaceName(),
+                    $document->getLocale(),
+                    \get_class($document),
+                    $document->getUuid()
+                ),
+                PermissionTypes::VIEW
+            );
+        }
+    }
+}

programming_language: PHP
filename: services.xml
change_type: ModificationType.MODIFY
diff: @@ -299,6 +299,13 @@
             <tag name="kernel.event_subscriber"/>
         </service>
 
+        <service id="sulu_website.event_listener.security_listener"
+                 class="Sulu\Bundle\WebsiteBundle\EventListener\SecurityListener">
+            <argument type="service" id="sulu_security.security_checker" on-invalid="null"/>
+
+            <tag name="kernel.event_subscriber"/>
+        </service>
+
         <!-- reference-store -->
         <service id="sulu_website.reference_store_pool"
                  class="Sulu\Bundle\WebsiteBundle\ReferenceStore\ReferenceStorePool">

programming_language: Ruby
filename: website.xml
change_type: ModificationType.MODIFY
diff: @@ -21,7 +21,7 @@
             <argument type="service" id="sulu.content.structure_manager"/>
             <argument type="service" id="sulu_core.webspace.webspace_manager"/>
             <argument type="service" id="sulu_core.webspace.request_analyzer"/>
-            <argument type="service" id="sulu_security.security_checker" on-invalid="null"/>
+            <argument>null</argument><!-- not longer required -->
             <argument type="collection" />
 
             <tag name="sulu.context" context="website"/>

programming_language: Ruby
filename: ContentRouteProvider.php
change_type: ModificationType.MODIFY
diff: @@ -24,9 +24,7 @@ use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
 use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
 use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
 use Sulu\Component\DocumentManager\DocumentManagerInterface;
-use Sulu\Component\Security\Authorization\PermissionTypes;
 use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
-use Sulu\Component\Security\Authorization\SecurityCondition;
 use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
 use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
 use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
@@ -34,6 +32,7 @@ use Symfony\Cmf\Component\Routing\RouteProviderInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\Routing\RouteCollection;
+use Webmozart\Assert\Assert;
 
 /**
  * The PortalRouteProvider should load the dynamic routes created by Sulu.
@@ -97,6 +96,7 @@ class ContentRouteProvider implements RouteProviderInterface
         $this->webspaceManager = $webspaceManager;
         $this->requestAnalyzer = $requestAnalyzer;
         $this->securityChecker = $securityChecker;
+        Assert::null($securityChecker, 'The security checker should be called by the SecurityListener not the ContentRouteProvider.'); // people who overwrite the ContentRouteProvider should make aware of that they also need to refactor this
         $this->defaultOptions = $defaultOptions;
     }
 
@@ -166,18 +166,6 @@ class ContentRouteProvider implements RouteProviderInterface
                 return $collection;
             }
 
-            if ($this->securityChecker && $portal->getWebspace()->hasWebsiteSecurity()) {
-                $this->securityChecker->checkPermission(
-                    new SecurityCondition(
-                        'sulu.webspaces.' . $document->getWebspaceName(),
-                        $document->getLocale(),
-                        \get_class($document),
-                        $document->getUuid()
-                    ),
-                    PermissionTypes::VIEW
-                );
-            }
-
             if (\preg_match('/\/$/', $resourceLocator) && ('/' !== $resourceLocator || $prefix)) {
                 // redirect page to page without slash at the end
                 $url = $prefix . \rtrim($resourceLocator, '/');

programming_language: PHP
filename: Kernel.php
change_type: ModificationType.MODIFY
diff: @@ -12,10 +12,16 @@
 namespace Sulu\Bundle\WebsiteBundle\Tests\Application;
 
 use Sulu\Bundle\TestBundle\Kernel\SuluTestKernel;
+use Symfony\Bundle\SecurityBundle\SecurityBundle;
 use Symfony\Component\Config\Loader\LoaderInterface;
 
 class Kernel extends SuluTestKernel
 {
+    /**
+     * @var string
+     */
+    private $appContext;
+
     /**
      * @param string $environment
      * @param bool $debug
@@ -23,7 +29,10 @@ class Kernel extends SuluTestKernel
      */
     public function __construct($environment, $debug, $suluContext = self::CONTEXT_ADMIN)
     {
-        parent::__construct($environment, $debug, $suluContext);
+        $envParts = \explode('_', $environment, 2);
+        $this->appContext = $envParts[1] ?? '';
+
+        parent::__construct($envParts[0], $debug, $suluContext);
     }
 
     public function registerContainerConfiguration(LoaderInterface $loader): void
@@ -32,5 +41,35 @@ class Kernel extends SuluTestKernel
 
         $context = $this->getContext();
         $loader->load(__DIR__ . '/config/config_' . $context . '.yml');
+        if ('' !== $this->appContext) {
+            $loader->load(__DIR__ . '/config/config_' . $this->appContext . '.yml');
+        }
+    }
+
+    public function registerBundles(): iterable
+    {
+        $bundles = parent::registerBundles();
+
+        if ('with_security' === $this->appContext) {
+            $bundles[] = new SecurityBundle();
+        }
+
+        return $bundles;
+    }
+
+    /**
+     * @return string
+     */
+    public function getCacheDir()
+    {
+        return parent::getCacheDir() . \ltrim('/' . $this->appContext);
+    }
+
+    /**
+     * @return string
+     */
+    public function getCommonCacheDir()
+    {
+        return parent::getCommonCacheDir() . \ltrim('/' . $this->appContext);
     }
 }

programming_language: PHP
filename: config_with_security.yml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,40 @@
+security:
+    enable_authenticator_manager: true
+
+    access_decision_manager:
+        strategy: unanimous
+        allow_if_all_abstain: true
+
+    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
+    password_hashers:
+        Sulu\Bundle\SecurityBundle\Entity\User: bcrypt
+
+    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
+    providers:
+        sulu:
+            id: sulu_security.user_provider
+
+    firewalls:
+        website:
+            pattern: ^/
+            lazy: true
+            provider: sulu
+            # The login and logout routes need to be created.
+            # For an advanced user management with registration and opt-in emails have a look at the:
+            # https://github.com/sulu/SuluCommunityBundle
+            # Also have a look at the user context based caching when you output user role specific data
+            # https://docs.sulu.io/en/2.2/cookbook/user-context-caching.html
+            form_login:
+                login_path: login
+                check_path: login
+            logout:
+                path: logout
+                target: /
+            remember_me:
+                secret:   "%kernel.secret%"
+                lifetime: 604800 # 1 week in seconds
+                path:     /
+
+sulu_security:
+    checker:
+        enabled: true

programming_language: Markdown
filename: routing_website.yml
change_type: ModificationType.MODIFY
diff: @@ -15,3 +15,9 @@ sulu_media:
 _portal_loader_test:
     resource: "routing_portal_loader_test.yml"
     type: portal
+
+login:
+    path: /login
+    controller:    Symfony\Bundle\FrameworkBundle\Controller\TemplateController
+    defaults:
+        template: login.html.twig

programming_language: Markdown
filename: sulu_lo.xml
change_type: ModificationType.MODIFY
diff: @@ -89,4 +89,3 @@
         </portal>
     </portals>
 </webspace>
-

programming_language: HTML
filename: SecurityListenerTest.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,152 @@
+<?php
+
+/*
+ * This file is part of Sulu.
+ *
+ * (c) Sulu GmbH
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Sulu\Bundle\WebsiteBundle\Tests\Functional;
+
+use Sulu\Bundle\PageBundle\Document\BasePageDocument;
+use Sulu\Bundle\PageBundle\Document\PageDocument;
+use Sulu\Bundle\SecurityBundle\Entity\AccessControl;
+use Sulu\Bundle\SecurityBundle\Entity\Permission;
+use Sulu\Bundle\SecurityBundle\Entity\Role;
+use Sulu\Bundle\TestBundle\Testing\SuluTestCase;
+use Sulu\Bundle\WebsiteBundle\Tests\Application\Kernel;
+use Sulu\Component\Content\Document\Behavior\SecurityBehavior;
+use Sulu\Component\Content\Document\WorkflowStage;
+use Symfony\Bundle\FrameworkBundle\KernelBrowser;
+
+/**
+ * @runTestsInSeparateProcesses This is necessary because the kernel is booted with different configurations.
+ */
+class SecurityListenerTest extends SuluTestCase
+{
+    /**
+     * @var KernelBrowser
+     */
+    private $client;
+
+    protected function setUp(): void
+    {
+        if (Kernel::VERSION_ID < 50000) { // @phpstan-ignore-line
+            $this->markTestSkipped('This test is only for Symfony 5.0 and above');
+        }
+
+        $this->client = $this->createWebsiteClient(['environment' => 'test_with_security']);
+        $this->purgeDatabase();
+        $this->initPhpcr();
+    }
+
+    public function testNoPermissions(): void
+    {
+        $pageDocument = $this->createSecuredPage();
+
+        $this->client->request('GET', 'http://sulu.lo/');
+        $response = $this->client->getResponse();
+
+        $this->assertSame(200, $response->getStatusCode());
+        $this->assertNull($response->headers->get('Location'));
+    }
+
+    public function testRedirectToLoginWhenNoAccess(): void
+    {
+        $pageDocument = $this->createSecuredPage();
+
+        $this->client->request('GET', 'http://sulu.lo/secure-area');
+        $response = $this->client->getResponse();
+
+        $this->assertSame(302, $response->getStatusCode());
+        $this->assertSame('http://sulu.lo/login', $response->headers->get('Location'));
+    }
+
+    private function createSecuredPage(): BasePageDocument
+    {
+        $documentManager = $this->getContainer()->get('sulu_document_manager.document_manager');
+        $entityManager = $this->getEntityManager();
+
+        $pageData = [
+            'locale' => 'en',
+            'title' => 'Secure Area',
+            'url' => '/secure-area',
+            'article' => '<p>Some sample text for this super secret area.</p>',
+            'structureType' => 'default',
+        ];
+
+        $extensionData = [
+            'seo' => [],
+            'excerpt' => [],
+        ];
+
+        /** @var PageDocument $pageDocument */
+        $pageDocument = $documentManager->create('page');
+
+        $pageDocument->setNavigationContexts([]);
+        $pageDocument->setLocale($pageData['locale']);
+        $pageDocument->setTitle($pageData['title']);
+        $pageDocument->setResourceSegment($pageData['url']);
+        $pageDocument->setStructureType($pageData['structureType']);
+        $pageDocument->setWorkflowStage(WorkflowStage::PUBLISHED);
+        $pageDocument->getStructure()->bind($pageData);
+        $pageDocument->setAuthor(1);
+        $pageDocument->setExtensionsData($extensionData);
+        $pageDocument->setPermissions([
+            1 => [ // do not allow anonymous users to access this page
+                'view' => false,
+            ],
+        ]);
+
+        $documentManager->persist(
+            $pageDocument,
+            'en',
+            ['parent_path' => '/cmf/sulu_io/contents']
+        );
+        $documentManager->flush();
+
+        // We need to add access control here as we do not have the document id before
+        $role = new Role();
+        $role->setName('Anonymous User Website');
+        $role->setSystem('sulu_io');
+        $role->setAnonymous(true);
+
+        $permission = new Permission();
+        $permission->setRole($role);
+        $permission->setPermissions(127);
+        $permission->setContext('sulu.webspaces.sulu_io');
+        $role->addPermission($permission);
+
+        $accessControl = new AccessControl();
+        $accessControl->setPermissions(0);
+        $accessControl->setEntityId($pageDocument->getUuid());
+        $accessControl->setEntityClass(SecurityBehavior::class);
+        $accessControl->setRole($role);
+
+        $entityManager->persist($permission);
+        $entityManager->persist($role);
+        $entityManager->persist($accessControl);
+        $entityManager->flush();
+
+        $pageDocument->setPermissions([
+            $role->getId() => [ // do not allow anonymous users to access this page
+                'view' => false,
+            ],
+        ]);
+        $documentManager->persist(
+            $pageDocument,
+            'en',
+            ['parent_path' => '/cmf/sulu_io/contents']
+        );
+        $documentManager->flush();
+        $documentManager->publish($pageDocument, 'en');
+
+        $documentManager->clear();
+        $entityManager->clear();
+
+        return $pageDocument;
+    }
+}

programming_language: PHP
filename: ContentRouteProviderTest.php
change_type: ModificationType.MODIFY
diff: @@ -35,15 +35,12 @@ use Sulu\Component\DocumentManager\Document\UnknownDocument;
 use Sulu\Component\DocumentManager\DocumentManagerInterface;
 use Sulu\Component\DocumentManager\Metadata;
 use Sulu\Component\Localization\Localization;
-use Sulu\Component\Security\Authorization\PermissionTypes;
 use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
-use Sulu\Component\Security\Authorization\SecurityCondition;
 use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
 use Sulu\Component\Webspace\Analyzer\RequestAnalyzer;
 use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
 use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
 use Sulu\Component\Webspace\Portal;
-use Sulu\Component\Webspace\Security;
 use Sulu\Component\Webspace\Segment;
 use Sulu\Component\Webspace\Webspace;
 use Symfony\Component\HttpFoundation\Request;
@@ -184,161 +181,6 @@ class ContentRouteProviderTest extends TestCase
         $this->assertEquals(false, $defaults['partial']);
     }
 
-    public function testSecurityChecker(): void
-    {
-        $attributes = $this->prophesize(RequestAttributes::class);
-
-        $attributes->getAttribute('localization', null)->willReturn(new Localization('de'));
-
-        $portal = new Portal();
-        $portal->setKey('portal');
-        $webspace = new Webspace();
-        $webspace->setKey('webspace');
-        $webspace->setTheme('theme');
-        $security = new Security();
-        $security->setSystem('website');
-        $security->setPermissionCheck(true);
-        $webspace->setSecurity($security);
-        $portal->setWebspace($webspace);
-        $attributes->getAttribute('portal', null)->willReturn($portal);
-
-        $attributes->getAttribute('matchType', null)->willReturn(RequestAnalyzer::MATCH_TYPE_FULL);
-        $attributes->getAttribute('resourceLocator', null)->willReturn(null);
-        $attributes->getAttribute('resourceLocatorPrefix', null)->willReturn('/de');
-
-        $this->resourceLocatorStrategy->loadByResourceLocator('', 'webspace', 'de')->willReturn('some-uuid');
-
-        $document = $this->prophesize(TitleBehavior::class)
-            ->willImplement(ExtensionBehavior::class)
-            ->willImplement(RedirectTypeBehavior::class)
-            ->willImplement(StructureBehavior::class)
-            ->willImplement(WebspaceBehavior::class)
-            ->willImplement(UuidBehavior::class);
-        $document->getUuid()->willReturn('some-uuid');
-        $document->getTitle()->willReturn('some-title');
-        $document->getWebspaceName()->willReturn('webspace');
-        $document->getLocale()->willReturn('de');
-        $document->getRedirectType()->willReturn(RedirectType::NONE);
-        $document->getStructureType()->willReturn('default');
-        $document->getUuid()->willReturn('some-uuid');
-        $document->getExtensionsData()->willReturn(['excerpt' => ['segments' => null]]);
-        $this->documentManager->find('some-uuid', 'de', ['load_ghost_content' => false])->willReturn($document->reveal());
-
-        $metadata = new Metadata();
-        $metadata->setAlias('page');
-        $structureMetadata = new StructureMetadata();
-        $this->documentInspector->getMetadata($document->reveal())->willReturn($metadata);
-        $this->documentInspector->getStructureMetadata($document->reveal())->willReturn($structureMetadata);
-
-        $pageBridge = $this->prophesize(PageBridge::class);
-        $pageBridge->getController()->willReturn('::Controller');
-        $this->structureManager->wrapStructure('page', $structureMetadata)->willReturn($pageBridge->reveal());
-
-        $request = new Request(
-            [],
-            [],
-            ['_sulu' => $attributes->reveal()],
-            [],
-            [],
-            ['REQUEST_URI' => \rawurlencode('/de')]
-        );
-
-        $pageBridge->setDocument($document->reveal())->shouldBeCalled();
-
-        $securityChecker = $this->prophesize(SecurityCheckerInterface::class);
-        $securityChecker->checkPermission(Argument::that(function(SecurityCondition $securityCondition) use ($document) {
-            $this->assertSame('some-uuid', $securityCondition->getObjectId());
-            $this->assertSame(\get_class($document->reveal()), $securityCondition->getObjectType());
-            $this->assertSame('de', $securityCondition->getLocale());
-            $this->assertSame('sulu.webspaces.webspace', $securityCondition->getSecurityContext());
-
-            return true;
-        }), PermissionTypes::VIEW)->shouldBeCalled();
-
-        $contentRouteProvider = $this->createContentRouteProvider($securityChecker->reveal());
-        $routes = $contentRouteProvider->getRouteCollectionForRequest($request);
-
-        $defaults = $routes->getIterator()->current()->getDefaults();
-
-        $this->assertCount(1, $routes);
-        $this->assertEquals($pageBridge->reveal(), $defaults['structure']);
-        $this->assertEquals(false, $defaults['partial']);
-    }
-
-    public function testSecurityCheckerWithoutPermissionCheck(): void
-    {
-        $attributes = $this->prophesize(RequestAttributes::class);
-
-        $attributes->getAttribute('localization', null)->willReturn(new Localization('de'));
-
-        $portal = new Portal();
-        $portal->setKey('portal');
-        $webspace = new Webspace();
-        $webspace->setKey('webspace');
-        $webspace->setTheme('theme');
-        $security = new Security();
-        $security->setSystem('website');
-        $security->setPermissionCheck(false);
-        $webspace->setSecurity($security);
-        $portal->setWebspace($webspace);
-        $attributes->getAttribute('portal', null)->willReturn($portal);
-
-        $attributes->getAttribute('matchType', null)->willReturn(RequestAnalyzer::MATCH_TYPE_FULL);
-        $attributes->getAttribute('resourceLocator', null)->willReturn(null);
-        $attributes->getAttribute('resourceLocatorPrefix', null)->willReturn('/de');
-
-        $this->resourceLocatorStrategy->loadByResourceLocator('', 'webspace', 'de')->willReturn('some-uuid');
-
-        $document = $this->prophesize(TitleBehavior::class)
-            ->willImplement(ExtensionBehavior::class)
-            ->willImplement(RedirectTypeBehavior::class)
-            ->willImplement(StructureBehavior::class)
-            ->willImplement(WebspaceBehavior::class)
-            ->willImplement(UuidBehavior::class);
-        $document->getUuid()->willReturn('some-uuid');
-        $document->getTitle()->willReturn('some-title');
-        $document->getWebspaceName()->willReturn('webspace');
-        $document->getLocale()->willReturn('de');
-        $document->getRedirectType()->willReturn(RedirectType::NONE);
-        $document->getStructureType()->willReturn('default');
-        $document->getUuid()->willReturn('some-uuid');
-        $document->getExtensionsData()->willReturn(['excerpt' => ['segments' => null]]);
-        $this->documentManager->find('some-uuid', 'de', ['load_ghost_content' => false])->willReturn($document->reveal());
-
-        $metadata = new Metadata();
-        $metadata->setAlias('page');
-        $structureMetadata = new StructureMetadata();
-        $this->documentInspector->getMetadata($document->reveal())->willReturn($metadata);
-        $this->documentInspector->getStructureMetadata($document->reveal())->willReturn($structureMetadata);
-
-        $pageBridge = $this->prophesize(PageBridge::class);
-        $pageBridge->getController()->willReturn('::Controller');
-        $this->structureManager->wrapStructure('page', $structureMetadata)->willReturn($pageBridge->reveal());
-
-        $request = new Request(
-            [],
-            [],
-            ['_sulu' => $attributes->reveal()],
-            [],
-            [],
-            ['REQUEST_URI' => \rawurlencode('/de')]
-        );
-
-        $pageBridge->setDocument($document->reveal())->shouldBeCalled();
-
-        $securityChecker = $this->prophesize(SecurityCheckerInterface::class);
-        $securityChecker->checkPermission(Argument::cetera())->shouldNotBeCalled();
-
-        $contentRouteProvider = $this->createContentRouteProvider($securityChecker->reveal());
-        $routes = $contentRouteProvider->getRouteCollectionForRequest($request);
-
-        $defaults = $routes->getIterator()->current()->getDefaults();
-
-        $this->assertCount(1, $routes);
-        $this->assertEquals($pageBridge->reveal(), $defaults['structure']);
-        $this->assertEquals(false, $defaults['partial']);
-    }
-
     public function testGetCollectionForRequestWithWrongSegment(): void
     {
         $attributes = $this->prophesize(RequestAttributes::class);

programming_language: PHP
filename: SystemRequestProcessor.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,76 @@
+<?php
+
+/*
+ * This file is part of Sulu.
+ *
+ * (c) Sulu GmbH
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Sulu\Component\Webspace\Analyzer\Attributes;
+
+use Sulu\Bundle\AdminBundle\Admin\Admin;
+use Sulu\Bundle\SecurityBundle\System\SystemStoreInterface;
+use Sulu\Component\HttpKernel\SuluKernel;
+use Sulu\Component\Webspace\PortalInformation;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @internal
+ * @final
+ *
+ * This class is internal overriding or extending should not be required instead create an own RequestProcessor.
+ */
+class SystemRequestProcessor implements RequestProcessorInterface
+{
+    /**
+     * @var SystemStoreInterface
+     */
+    private $systemStore;
+
+    /**
+     * @var string
+     */
+    private $context;
+
+    public function __construct(
+        SystemStoreInterface $systemStore,
+        string $context
+    ) {
+        $this->systemStore = $systemStore;
+        $this->context = $context;
+    }
+
+    public function process(Request $request, RequestAttributes $requestAttributes)
+    {
+        $attributes = [];
+        if (SuluKernel::CONTEXT_ADMIN === $this->context) {
+            $this->systemStore->setSystem(Admin::SULU_ADMIN_SECURITY_SYSTEM);
+            $attributes['system'] = Admin::SULU_ADMIN_SECURITY_SYSTEM;
+
+            return new RequestAttributes($attributes);
+        }
+        $portalInformation = $requestAttributes->getAttribute('portalInformation');
+
+        if (!$portalInformation instanceof PortalInformation) {
+            return new RequestAttributes($attributes);
+        }
+
+        $webspace = $portalInformation->getWebspace();
+
+        $security = $webspace->getSecurity();
+        if ($security) {
+            $attributes['system'] = $security->getSystem();
+            $this->systemStore->setSystem($attributes['system']);
+        }
+
+        return new RequestAttributes($attributes);
+    }
+
+    public function validate(RequestAttributes $attributes)
+    {
+        return true;
+    }
+}

programming_language: PHP