Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions src/bundle/Controller/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@

abstract class Controller extends AbstractController
{
public function performAccessCheck(): void
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
}
}
6 changes: 5 additions & 1 deletion src/bundle/Controller/PasswordChangeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

use Exception;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
use Ibexa\Core\MVC\Symfony\SiteAccess;
use Ibexa\User\ExceptionHandler\ActionResultHandler;
use Ibexa\User\Form\Factory\FormFactory;
Expand All @@ -20,8 +22,10 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class PasswordChangeController extends Controller
final class PasswordChangeController extends Controller implements RestrictedControllerInterface
{
use AuthenticatedRememberedCheckTrait;

private ActionResultHandler $actionResultHandler;

private UserService $userService;
Expand Down
6 changes: 5 additions & 1 deletion src/bundle/Controller/UserInvitationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace Ibexa\Bundle\User\Controller;

use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
use Ibexa\Contracts\User\Invitation\Exception\InvitationAlreadyExistsException;
use Ibexa\Contracts\User\Invitation\Exception\UserAlreadyExistsException;
use Ibexa\Contracts\User\Invitation\InvitationCreateStruct;
Expand All @@ -20,8 +22,10 @@
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;

final class UserInvitationController extends Controller
final class UserInvitationController extends Controller implements RestrictedControllerInterface
{
use AuthenticatedRememberedCheckTrait;

private InvitationService $invitationService;

private InvitationSender $mailSender;
Expand Down
6 changes: 5 additions & 1 deletion src/bundle/Controller/UserSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
namespace Ibexa\Bundle\User\Controller;

use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
use Ibexa\User\ExceptionHandler\ActionResultHandler;
use Ibexa\User\Form\Data\UserSettingUpdateData;
use Ibexa\User\Form\Factory\FormFactory;
Expand All @@ -24,8 +26,10 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class UserSettingsController extends Controller
final class UserSettingsController extends Controller implements RestrictedControllerInterface
{
use AuthenticatedRememberedCheckTrait;

private FormFactory $formFactory;

private SubmitHandler $submitHandler;
Expand Down
1 change: 0 additions & 1 deletion src/bundle/Resources/config/services/controllers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ services:
Ibexa\Bundle\User\Controller\Controller:
calls:
- [setContainer , ['@Psr\Container\ContainerInterface']]
- [performAccessCheck, []]

Ibexa\Bundle\User\Controller\PasswordResetController:
calls:
Expand Down
17 changes: 17 additions & 0 deletions src/contracts/Controller/AuthenticatedRememberedCheckTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\User\Controller;

trait AuthenticatedRememberedCheckTrait
{
public function performAccessCheck(): void
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
}
}
14 changes: 14 additions & 0 deletions src/contracts/Controller/RestrictedControllerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Contracts\User\Controller;

interface RestrictedControllerInterface
{
public function performAccessCheck(): void;
}
60 changes: 60 additions & 0 deletions src/lib/EventListener/PerformAccessCheckSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\User\EventListener;

use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;

final class PerformAccessCheckSubscriber implements EventSubscriberInterface
{
/**
* @param iterable<object> $controllers
*/
public function __construct(
#[AutowireIterator('controller.service_arguments')]
private readonly iterable $controllers
) {
}

public function onControllerEvent(ControllerEvent $event): void
{
$controller = $event->getController();
if (is_array($controller) && $controller[0] instanceof RestrictedControllerInterface) {
$controller[0]->performAccessCheck();

return;
}

if ($controller instanceof RestrictedControllerInterface) {
$controller->performAccessCheck();

return;
}

if (is_string($controller) && str_contains($controller, '::')) {
[$class] = explode('::', $controller, 2);

foreach ($this->controllers as $controllerInstance) {
if ($controllerInstance::class === $class && $controllerInstance instanceof RestrictedControllerInterface) {
$controllerInstance->performAccessCheck();
break;
}
}
}
}

public static function getSubscribedEvents(): array
{
return [
ControllerEvent::class => 'onControllerEvent',
];
}
}
79 changes: 79 additions & 0 deletions tests/lib/EventListener/PerformAccessCheckSubscriberTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\User\EventListener;

use Ibexa\Tests\User\Stub\RestrictedControllerStub;
use Ibexa\User\EventListener\PerformAccessCheckSubscriber;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;

final class PerformAccessCheckSubscriberTest extends TestCase
{
private PerformAccessCheckSubscriber $subscriber;

private HttpKernelInterface $kernel;

private Request $request;

protected function setUp(): void
{
$this->kernel = $this->createMock(HttpKernelInterface::class);
$this->request = new Request();
$this->subscriber = new PerformAccessCheckSubscriber([]);
}

public function testArrayController(): void
{
$controller = new RestrictedControllerStub();
$event = new ControllerEvent(
$this->kernel,
[$controller, 'action'],
$this->request,
HttpKernelInterface::MAIN_REQUEST
);

$this->subscriber->onControllerEvent($event);

self::assertTrue($controller->wasCheckPerformed());
}

public function testInvokableController(): void
{
$controller = new RestrictedControllerStub();
$event = new ControllerEvent(
$this->kernel,
$controller,
$this->request,
HttpKernelInterface::MAIN_REQUEST
);

$this->subscriber->onControllerEvent($event);

self::assertTrue($controller->wasCheckPerformed());
}

public function testStringControllerWithServiceLookup(): void
{
$controller = new RestrictedControllerStub();
$this->subscriber = new PerformAccessCheckSubscriber([$controller]);

$event = new ControllerEvent(
$this->kernel,
RestrictedControllerStub::class . '::staticAction',
$this->request,
HttpKernelInterface::MAIN_REQUEST
);

$this->subscriber->onControllerEvent($event);

self::assertTrue($controller->wasCheckPerformed());
}
}
38 changes: 38 additions & 0 deletions tests/lib/Stub/RestrictedControllerStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\User\Stub;

use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;

final class RestrictedControllerStub implements RestrictedControllerInterface
{
private bool $checkPerformed = false;

public function performAccessCheck(): void
{
$this->checkPerformed = true;
}

public function wasCheckPerformed(): bool
{
return $this->checkPerformed;
}

public function __invoke(): void
{
}

public function action(): void
{
}

public static function staticAction(): void
{
}
}
Loading