Skip to content

Commit cfff315

Browse files
authored
IBX-10170: Introduced AccessCheckController contract (#111)
* IBX-10170: Introduced AccessCheckController contract * IBX-10170: renamed to RestrictedControllerInterface * IBX-10170: Make access check work with all types of controller references * cs fix * added phpdoc * moved from ControllerArgumentsEvent to ControllerEvent * applied cr remarks
1 parent f96cca6 commit cfff315

File tree

10 files changed

+223
-8
lines changed

10 files changed

+223
-8
lines changed

src/bundle/Controller/Controller.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,4 @@
1212

1313
abstract class Controller extends AbstractController
1414
{
15-
public function performAccessCheck(): void
16-
{
17-
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
18-
}
1915
}

src/bundle/Controller/PasswordChangeController.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
use Exception;
1212
use Ibexa\Contracts\Core\Repository\UserService;
13+
use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
14+
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
1315
use Ibexa\Core\MVC\Symfony\SiteAccess;
1416
use Ibexa\User\ExceptionHandler\ActionResultHandler;
1517
use Ibexa\User\Form\Factory\FormFactory;
@@ -20,8 +22,10 @@
2022
use Symfony\Component\HttpFoundation\Request;
2123
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2224

23-
class PasswordChangeController extends Controller
25+
final class PasswordChangeController extends Controller implements RestrictedControllerInterface
2426
{
27+
use AuthenticatedRememberedCheckTrait;
28+
2529
private ActionResultHandler $actionResultHandler;
2630

2731
private UserService $userService;

src/bundle/Controller/UserInvitationController.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
namespace Ibexa\Bundle\User\Controller;
1010

11+
use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
12+
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
1113
use Ibexa\Contracts\User\Invitation\Exception\InvitationAlreadyExistsException;
1214
use Ibexa\Contracts\User\Invitation\Exception\UserAlreadyExistsException;
1315
use Ibexa\Contracts\User\Invitation\InvitationCreateStruct;
@@ -20,8 +22,10 @@
2022
use Symfony\Component\Form\FormFactoryInterface;
2123
use Symfony\Component\HttpFoundation\Request;
2224

23-
final class UserInvitationController extends Controller
25+
final class UserInvitationController extends Controller implements RestrictedControllerInterface
2426
{
27+
use AuthenticatedRememberedCheckTrait;
28+
2529
private InvitationService $invitationService;
2630

2731
private InvitationSender $mailSender;

src/bundle/Controller/UserSettingsController.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
namespace Ibexa\Bundle\User\Controller;
1010

1111
use Ibexa\Contracts\Core\Repository\PermissionResolver;
12+
use Ibexa\Contracts\User\Controller\AuthenticatedRememberedCheckTrait;
13+
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
1214
use Ibexa\User\ExceptionHandler\ActionResultHandler;
1315
use Ibexa\User\Form\Data\UserSettingUpdateData;
1416
use Ibexa\User\Form\Factory\FormFactory;
@@ -24,8 +26,10 @@
2426
use Symfony\Component\HttpFoundation\Request;
2527
use Symfony\Component\HttpFoundation\Response;
2628

27-
class UserSettingsController extends Controller
29+
final class UserSettingsController extends Controller implements RestrictedControllerInterface
2830
{
31+
use AuthenticatedRememberedCheckTrait;
32+
2933
private FormFactory $formFactory;
3034

3135
private SubmitHandler $submitHandler;

src/bundle/Resources/config/services/controllers.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ services:
1717
Ibexa\Bundle\User\Controller\Controller:
1818
calls:
1919
- [setContainer , ['@Psr\Container\ContainerInterface']]
20-
- [performAccessCheck, []]
2120

2221
Ibexa\Bundle\User\Controller\PasswordResetController:
2322
calls:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\User\Controller;
10+
11+
trait AuthenticatedRememberedCheckTrait
12+
{
13+
public function performAccessCheck(): void
14+
{
15+
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
16+
}
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Contracts\User\Controller;
10+
11+
interface RestrictedControllerInterface
12+
{
13+
public function performAccessCheck(): void;
14+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\User\EventListener;
10+
11+
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
12+
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
13+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
15+
16+
final class PerformAccessCheckSubscriber implements EventSubscriberInterface
17+
{
18+
/**
19+
* @param iterable<object> $controllers
20+
*/
21+
public function __construct(
22+
#[AutowireIterator('controller.service_arguments')]
23+
private readonly iterable $controllers
24+
) {
25+
}
26+
27+
public function onControllerEvent(ControllerEvent $event): void
28+
{
29+
$controller = $event->getController();
30+
if (is_array($controller) && $controller[0] instanceof RestrictedControllerInterface) {
31+
$controller[0]->performAccessCheck();
32+
33+
return;
34+
}
35+
36+
if ($controller instanceof RestrictedControllerInterface) {
37+
$controller->performAccessCheck();
38+
39+
return;
40+
}
41+
42+
if (is_string($controller) && str_contains($controller, '::')) {
43+
[$class] = explode('::', $controller, 2);
44+
45+
foreach ($this->controllers as $controllerInstance) {
46+
if ($controllerInstance::class === $class && $controllerInstance instanceof RestrictedControllerInterface) {
47+
$controllerInstance->performAccessCheck();
48+
break;
49+
}
50+
}
51+
}
52+
}
53+
54+
public static function getSubscribedEvents(): array
55+
{
56+
return [
57+
ControllerEvent::class => 'onControllerEvent',
58+
];
59+
}
60+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\User\EventListener;
10+
11+
use Ibexa\Tests\User\Stub\RestrictedControllerStub;
12+
use Ibexa\User\EventListener\PerformAccessCheckSubscriber;
13+
use PHPUnit\Framework\TestCase;
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Event\ControllerEvent;
16+
use Symfony\Component\HttpKernel\HttpKernelInterface;
17+
18+
final class PerformAccessCheckSubscriberTest extends TestCase
19+
{
20+
private PerformAccessCheckSubscriber $subscriber;
21+
22+
private HttpKernelInterface $kernel;
23+
24+
private Request $request;
25+
26+
protected function setUp(): void
27+
{
28+
$this->kernel = $this->createMock(HttpKernelInterface::class);
29+
$this->request = new Request();
30+
$this->subscriber = new PerformAccessCheckSubscriber([]);
31+
}
32+
33+
public function testArrayController(): void
34+
{
35+
$controller = new RestrictedControllerStub();
36+
$event = new ControllerEvent(
37+
$this->kernel,
38+
[$controller, 'action'],
39+
$this->request,
40+
HttpKernelInterface::MAIN_REQUEST
41+
);
42+
43+
$this->subscriber->onControllerEvent($event);
44+
45+
self::assertTrue($controller->wasCheckPerformed());
46+
}
47+
48+
public function testInvokableController(): void
49+
{
50+
$controller = new RestrictedControllerStub();
51+
$event = new ControllerEvent(
52+
$this->kernel,
53+
$controller,
54+
$this->request,
55+
HttpKernelInterface::MAIN_REQUEST
56+
);
57+
58+
$this->subscriber->onControllerEvent($event);
59+
60+
self::assertTrue($controller->wasCheckPerformed());
61+
}
62+
63+
public function testStringControllerWithServiceLookup(): void
64+
{
65+
$controller = new RestrictedControllerStub();
66+
$this->subscriber = new PerformAccessCheckSubscriber([$controller]);
67+
68+
$event = new ControllerEvent(
69+
$this->kernel,
70+
RestrictedControllerStub::class . '::staticAction',
71+
$this->request,
72+
HttpKernelInterface::MAIN_REQUEST
73+
);
74+
75+
$this->subscriber->onControllerEvent($event);
76+
77+
self::assertTrue($controller->wasCheckPerformed());
78+
}
79+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/**
4+
* @copyright Copyright (C) Ibexa AS. All rights reserved.
5+
* @license For full copyright and license information view LICENSE file distributed with this source code.
6+
*/
7+
declare(strict_types=1);
8+
9+
namespace Ibexa\Tests\User\Stub;
10+
11+
use Ibexa\Contracts\User\Controller\RestrictedControllerInterface;
12+
13+
final class RestrictedControllerStub implements RestrictedControllerInterface
14+
{
15+
private bool $checkPerformed = false;
16+
17+
public function performAccessCheck(): void
18+
{
19+
$this->checkPerformed = true;
20+
}
21+
22+
public function wasCheckPerformed(): bool
23+
{
24+
return $this->checkPerformed;
25+
}
26+
27+
public function __invoke(): void
28+
{
29+
}
30+
31+
public function action(): void
32+
{
33+
}
34+
35+
public static function staticAction(): void
36+
{
37+
}
38+
}

0 commit comments

Comments
 (0)