Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8f924b6

Browse files
committed
feature #7590 AssociationField: Check if user has permission to see the related entity (Seb33300)
This PR was squashed before being merged into the 5.x branch. Discussion ---------- AssociationField: Check if user has permission to see the related entity When using `AssociationField`, a link to the related entity detail page is displayed on the index page, but the check to verify if user has permission to open that page is missing: https://github.com/EasyCorp/EasyAdminBundle/blob/0846e653f6943df87a5f22b688f01917f86dcfbc/src/Field/Configurator/AssociationConfigurator.php#L301 This PR fixes the issue. If not permitted, data is displayed as plain text without link. Commits ------- 3faf727 AssociationField: Check if user has permission to see the related entity
2 parents 2909d66 + 3faf727 commit 8f924b6

5 files changed

Lines changed: 71 additions & 3 deletions

File tree

config/services.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,9 @@
374374
->arg(2, service('request_stack'))
375375
->arg(3, service(ControllerFactory::class))
376376
->arg(4, new Reference(FieldFactory::class))
377+
->arg(5, new Reference(AuthorizationChecker::class))
378+
->arg(6, service(AdminContextProvider::class))
379+
->arg(7, service(AdminContextFactory::class))
377380

378381
->set(AvatarConfigurator::class)
379382

src/Field/Configurator/AssociationConfigurator.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,25 @@
1212
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\TextAlign;
1313
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
1414
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldConfiguratorInterface;
15+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Provider\AdminContextProviderInterface;
16+
use EasyCorp\Bundle\EasyAdminBundle\Dto\CrudDto;
1517
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
1618
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
19+
use EasyCorp\Bundle\EasyAdminBundle\Factory\AdminContextFactory;
1720
use EasyCorp\Bundle\EasyAdminBundle\Factory\ControllerFactory;
1821
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory;
1922
use EasyCorp\Bundle\EasyAdminBundle\Factory\FieldFactory;
2023
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
2124
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudAutocompleteType;
2225
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\CrudFormType;
2326
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGeneratorInterface;
27+
use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;
28+
use Symfony\Component\HttpFoundation\Request;
2429
use Symfony\Component\HttpFoundation\RequestStack;
2530
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
2631
use Symfony\Component\PropertyAccess\PropertyAccessor;
2732
use Symfony\Component\Routing\Exception\RouteNotFoundException;
33+
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2834
use function Symfony\Component\Translation\t;
2935

3036
/**
@@ -38,6 +44,9 @@ public function __construct(
3844
private RequestStack $requestStack,
3945
private ControllerFactory $controllerFactory,
4046
private FieldFactory $fieldFactory,
47+
private AuthorizationCheckerInterface $authorizationChecker,
48+
private AdminContextProviderInterface $adminContextProvider,
49+
private AdminContextFactory $adminContextFactory,
4150
) {
4251
}
4352

@@ -298,7 +307,12 @@ private function generateLinkToAssociatedEntity(?string $crudController, EntityD
298307
// associated entity is null (e.g. admin_post_index and Post <-> User)
299308
$crudAction = null === $primaryKeyValue ? Action::INDEX : Action::DETAIL;
300309

301-
// TODO: check if user has permission to see the related entity
310+
$crudDto = $this->createCrudDto($crudController, $crudAction);
311+
312+
if (!$this->authorizationChecker->isGranted(Permission::EA_EXECUTE_ACTION, ['crud' => $crudDto, 'action' => $crudAction, 'entity' => $entityDto])) {
313+
return null;
314+
}
315+
302316
return $this->adminUrlGenerator
303317
->setController($crudController)
304318
->setAction($crudAction)
@@ -381,6 +395,34 @@ private function createEntityDto(string $entityFqcn, string $crudControllerFqcn,
381395
return $entityDto;
382396
}
383397

398+
/**
399+
* @param class-string $crudControllerFqcn
400+
*/
401+
private function createCrudDto(string $crudControllerFqcn, string $crudControllerAction): CrudDto
402+
{
403+
$request = new Request();
404+
405+
$dashboardController = $this->controllerFactory->getDashboardControllerInstance(
406+
$this->adminContextProvider->getContext()->getDashboardControllerFqcn(),
407+
$request,
408+
);
409+
410+
$crudController = $this->controllerFactory->getCrudControllerInstance(
411+
$crudControllerFqcn,
412+
$crudControllerAction,
413+
$request,
414+
);
415+
416+
$adminContext = $this->adminContextFactory->create(
417+
$request,
418+
$dashboardController,
419+
$crudController,
420+
$crudControllerAction,
421+
);
422+
423+
return $adminContext->getCrud();
424+
}
425+
384426
private function configurePreferredChoices(FieldDto $field): void
385427
{
386428
$preferredChoices = $field->getCustomOption(AssociationField::OPTION_PREFERRED_CHOICES);

src/Security/SecurityVoter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected function voteOnAttribute(string $permissionName, mixed $subject, Token
4141
}
4242

4343
if (Permission::EA_EXECUTE_ACTION === $permissionName) {
44-
return $this->voteOnExecuteActionPermission($this->adminContextProvider->getContext()->getCrud(), $subject['action'] ?? null, $subject['entity'] ?? null, $subject['entityFqcn'] ?? null);
44+
return $this->voteOnExecuteActionPermission($subject['crud'] ?? $this->adminContextProvider->getContext()->getCrud(), $subject['action'] ?? null, $subject['entity'] ?? null, $subject['entityFqcn'] ?? null);
4545
}
4646

4747
if (Permission::EA_VIEW_FIELD === $permissionName) {

tests/Unit/Field/AbstractFieldTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
99
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
1010
use EasyCorp\Bundle\EasyAdminBundle\Context\CrudContext;
11+
use EasyCorp\Bundle\EasyAdminBundle\Context\DashboardContext;
1112
use EasyCorp\Bundle\EasyAdminBundle\Context\I18nContext;
1213
use EasyCorp\Bundle\EasyAdminBundle\Context\RequestContext;
1314
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Context\AdminContextInterface;
@@ -16,6 +17,7 @@
1617
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
1718
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
1819
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
20+
use EasyCorp\Bundle\EasyAdminBundle\Tests\Functional\Apps\DefaultApp\Controller\DashboardController;
1921
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
2022
use Symfony\Component\HttpFoundation\Request;
2123
use Symfony\Component\HttpFoundation\RequestStack;
@@ -62,6 +64,7 @@ protected function getAdminContext(string $pageName, string $requestLocale, stri
6264
return $this->adminContext = AdminContext::forTesting(
6365
requestContext: RequestContext::forTesting($request),
6466
crudContext: CrudContext::forTesting($crudDto),
67+
dashboardContext: DashboardContext::forTesting(dashboardControllerFqcn: DashboardController::class),
6568
i18nContext: I18nContext::forTesting($requestLocale),
6669
);
6770
}

tests/Unit/Field/Configurator/AssociationConfiguratorTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
use Doctrine\ORM\EntityManagerInterface;
66
use Doctrine\ORM\Mapping\ClassMetadata;
77
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
8+
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
9+
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Context\AdminContextInterface;
810
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
911
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
12+
use EasyCorp\Bundle\EasyAdminBundle\Factory\AdminContextFactory;
1013
use EasyCorp\Bundle\EasyAdminBundle\Factory\ControllerFactory;
1114
use EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory;
1215
use EasyCorp\Bundle\EasyAdminBundle\Factory\FieldFactory;
1316
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
1417
use EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\AssociationConfigurator;
1518
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
19+
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
1620
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGeneratorInterface;
1721
use EasyCorp\Bundle\EasyAdminBundle\Tests\Functional\Apps\DefaultApp\Controller\ProjectDomain\DeveloperCrudController;
1822
use EasyCorp\Bundle\EasyAdminBundle\Tests\Functional\Apps\DefaultApp\Controller\ProjectDomain\ProjectCrudController;
@@ -24,10 +28,12 @@
2428
use EasyCorp\Bundle\EasyAdminBundle\Tests\Unit\Field\AbstractFieldTest;
2529
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
2630
use Symfony\Component\HttpFoundation\RequestStack;
31+
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
2732

2833
class AssociationConfiguratorTest extends AbstractFieldTest
2934
{
3035
private EntityDto $projectDto;
36+
private RequestStack $requestStack;
3137

3238
protected function setUp(): void
3339
{
@@ -37,12 +43,17 @@ protected function setUp(): void
3743

3844
$adminUrlGenerator = $this->getMockBuilder(AdminUrlGeneratorInterface::class)->disableOriginalConstructor()->getMock();
3945

46+
$this->requestStack = new RequestStack();
47+
4048
$this->configurator = new AssociationConfigurator(
4149
static::getContainer()->get(EntityFactory::class),
4250
$adminUrlGenerator,
43-
static::getContainer()->get(RequestStack::class),
51+
$this->requestStack,
4452
static::getContainer()->get(ControllerFactory::class),
4553
static::getContainer()->get(FieldFactory::class),
54+
static::getContainer()->get(AuthorizationCheckerInterface::class),
55+
new AdminContextProvider($this->requestStack),
56+
static::getContainer()->get(AdminContextFactory::class),
4657
);
4758
}
4859

@@ -51,6 +62,15 @@ protected function getEntityDto(): EntityDto
5162
return $this->projectDto;
5263
}
5364

65+
protected function getAdminContext(string $pageName, string $requestLocale, string $actionName, ?string $controllerFqcn = null): AdminContextInterface
66+
{
67+
$context = parent::getAdminContext($pageName, $requestLocale, $actionName, $controllerFqcn);
68+
$context->getRequest()->attributes->set(EA::CONTEXT_REQUEST_ATTRIBUTE, $context);
69+
$this->requestStack->push($context->getRequest());
70+
71+
return $context;
72+
}
73+
5474
public function testToOneAssociation(): void
5575
{
5676
$field = AssociationField::new('leadDeveloper');

0 commit comments

Comments
 (0)