Skip to content

Commit

Permalink
feature EasyCorp#6130 [FEATURE] Support use of Symfony Expressions fo…
Browse files Browse the repository at this point in the history
…r permissions (a-r-m-i-n)

This PR was merged into the 4.x branch.

Discussion
----------

[FEATURE] Support use of Symfony Expressions for permissions

Relates: EasyCorp#4864

To check if it works, you can add a simple expression like that:

```
->setPermission(new Expression('true'))
```
Without this patch this will deny access to defined resource (because the string "true" is no valid role name).

I've also added a new chapter in security.rst, with more details about possible usage.

Commits
-------

2db9e9d [FEATURE] Support use of Symfony Expressions for permissions
  • Loading branch information
javiereguiluz committed Feb 5, 2024
2 parents bc56953 + 2db9e9d commit 0b985d7
Show file tree
Hide file tree
Showing 13 changed files with 66 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .ddev/example/Controller/Admin/DashboardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

Expand All @@ -30,6 +31,6 @@ public function configureMenuItems(): iterable

yield MenuItem::section('Entities');
yield MenuItem::linkToCrud('Articles', 'fas fa-list', BlogArticle::class);
yield MenuItem::linkToCrud('Categories', 'fas fa-tag', Category::class);
yield MenuItem::linkToCrud('Categories', 'fas fa-tag', Category::class)->setPermission(new Expression('true'));
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"symfony/css-selector": "^5.4|^6.0|^7.0",
"symfony/debug-bundle": "^5.4|^6.0|^7.0",
"symfony/dom-crawler": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "^5.4|^6.0|^7.0"
},
"config": {
Expand Down
33 changes: 33 additions & 0 deletions doc/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,39 @@ permissions to see some items:
.. image:: images/easyadmin-list-hidden-results.png
:alt: Index page with some results hidden because user does not have enough permissions

.. _security-expressions:

Using expressions
-----------------

EasyAdmin supports for all permissions the usage of Symfony Expressions.
To use them you need to require the expression language component to your project, using Composer:

.. code-block:: terminal
$ composer require symfony/expression-language
Now, when defining permissions, instead of a role name string (like ``ROLE_ADMIN``) only,
you can pass an Symfony Expression object, like this:

.. code-block:: php
use Symfony\Component\ExpressionLanguage\Expression;
MenuItem::linkToCrud('Restricted menu-item', null, Example::class)
->setPermission(new Expression('"ROLE_DEVELOPER" in role_names and "ROLE_EXTERNAL" not in role_names'));
This allows you to define much more detailed permissions, based on several role names, user attributes or the given subject.

Available variables in expression are:

* ``user`` - the current user object
* ``role_names`` - all roles of current user as array
* ``subject`` or ``object`` - the current subject being checked
* ``token`` - authentication token
* ``trust_resolver`` - authentication trust resolver
* ``auth_checker`` - instance of auth_checker

Custom Security Voters
----------------------

Expand Down
3 changes: 2 additions & 1 deletion src/Config/Actions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace EasyCorp\Bundle\EasyAdminBundle\Config;

use EasyCorp\Bundle\EasyAdminBundle\Dto\ActionConfigDto;
use Symfony\Component\ExpressionLanguage\Expression;
use function Symfony\Component\Translation\t;

/**
Expand Down Expand Up @@ -100,7 +101,7 @@ public function reorder(string $pageName, array $orderedActionNames): self
return $this;
}

public function setPermission(string $actionName, string $permission): self
public function setPermission(string $actionName, string|Expression $permission): self
{
$this->dto->setActionPermission($actionName, $permission);

Expand Down
3 changes: 2 additions & 1 deletion src/Config/Crud.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Dto\FilterConfigDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\PaginatorDto;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Contracts\Translation\TranslatableInterface;

/**
Expand Down Expand Up @@ -390,7 +391,7 @@ public function setFormOptions(array $newFormOptions, ?array $editFormOptions =
return $this;
}

public function setEntityPermission(string $permission): self
public function setEntityPermission(string|Expression $permission): self
{
$this->dto->setEntityPermission($permission);

Expand Down
3 changes: 2 additions & 1 deletion src/Config/Menu/MenuItemTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace EasyCorp\Bundle\EasyAdminBundle\Config\Menu;

use EasyCorp\Bundle\EasyAdminBundle\Dto\MenuItemDto;
use Symfony\Component\ExpressionLanguage\Expression;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
Expand All @@ -25,7 +26,7 @@ public function setQueryParameter(string $parameterName, mixed $parameterValue):
return $this;
}

public function setPermission(string $permission): self
public function setPermission(string|Expression $permission): self
{
$this->dto->setPermission($permission);

Expand Down
5 changes: 3 additions & 2 deletions src/Dto/ActionConfigDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use EasyCorp\Bundle\EasyAdminBundle\Collection\ActionCollection;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use Symfony\Component\ExpressionLanguage\Expression;

/**
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
Expand All @@ -22,7 +23,7 @@ final class ActionConfigDto
];
/** @var string[] */
private array $disabledActions = [];
/** @var string[] */
/** @var string[]|Expression[] */
private array $actionPermissions = [];

public function __construct()
Expand All @@ -43,7 +44,7 @@ public function setPageName(?string $pageName): void
$this->pageName = $pageName;
}

public function setActionPermission(string $actionName, string $permission): void
public function setActionPermission(string $actionName, string|Expression $permission): void
{
$this->actionPermissions[$actionName] = $permission;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Dto/CrudDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\SearchMode;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Translation\TranslatableMessageBuilder;
use Symfony\Component\ExpressionLanguage\Expression;
use function Symfony\Component\Translation\t;
use Symfony\Contracts\Translation\TranslatableInterface;

Expand Down Expand Up @@ -60,7 +61,7 @@ final class CrudDto
private array $formThemes = ['@EasyAdmin/crud/form_theme.html.twig'];
private KeyValueStore $newFormOptions;
private KeyValueStore $editFormOptions;
private ?string $entityPermission = null;
private string|Expression|null $entityPermission = null;
private ?string $contentWidth = null;
private ?string $sidebarWidth = null;
private bool $hideNullValues = false;
Expand Down Expand Up @@ -437,12 +438,12 @@ public function setEditFormOptions(KeyValueStore $formOptions): void
$this->editFormOptions = $formOptions;
}

public function getEntityPermission(): ?string
public function getEntityPermission(): string|Expression|null
{
return $this->entityPermission;
}

public function setEntityPermission(string $entityPermission): void
public function setEntityPermission(string|Expression $entityPermission): void
{
$this->entityPermission = $entityPermission;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Dto/EntityDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Collection\ActionCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\PropertyAccess\PropertyAccess;

/**
Expand All @@ -21,14 +22,14 @@ final class EntityDto
private $instance;
private $primaryKeyName;
private mixed $primaryKeyValue = null;
private ?string $permission;
private string|Expression|null $permission;
private ?FieldCollection $fields = null;
private ?ActionCollection $actions = null;

/**
* @param ClassMetadata&ClassMetadataInfo $entityMetadata
*/
public function __construct(string $entityFqcn, ClassMetadata $entityMetadata, ?string $entityPermission = null, /* ?object */ $entityInstance = null)
public function __construct(string $entityFqcn, ClassMetadata $entityMetadata, string|Expression|null $entityPermission = null, /* ?object */ $entityInstance = null)
{
if (!\is_object($entityInstance)
&& null !== $entityInstance) {
Expand Down Expand Up @@ -112,7 +113,7 @@ public function getPrimaryKeyValueAsString(): string
return (string) $this->getPrimaryKeyValue();
}

public function getPermission(): ?string
public function getPermission(): string|Expression|null
{
return $this->permission;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Dto/FieldDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneGroupCloseType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneGroupOpenType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabPaneOpenType;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Uid\Ulid;
use Symfony\Contracts\Translation\TranslatableInterface;

Expand All @@ -34,7 +35,7 @@ final class FieldDto
private KeyValueStore $formTypeOptions;
private ?bool $sortable = null;
private ?bool $virtual = null;
private ?string $permission = null;
private string|Expression|null $permission = null;
private ?string $textAlign = null;
private $help;
private string $cssClass = '';
Expand Down Expand Up @@ -296,12 +297,12 @@ public function setTextAlign(string $textAlign): void
$this->textAlign = $textAlign;
}

public function getPermission(): ?string
public function getPermission(): string|Expression|null
{
return $this->permission;
}

public function setPermission(string $permission): void
public function setPermission(string|Expression $permission): void
{
$this->permission = $permission;
}
Expand Down
7 changes: 4 additions & 3 deletions src/Dto/MenuItemDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace EasyCorp\Bundle\EasyAdminBundle\Dto;

use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Contracts\Translation\TranslatableInterface;

/**
Expand All @@ -24,7 +25,7 @@ final class MenuItemDto
private TranslatableInterface|string|null $label = null;
private ?string $icon = null;
private string $cssClass = '';
private ?string $permission = null;
private string|Expression|null $permission = null;
private ?string $routeName = null;
private ?array $routeParameters = null;
private ?string $linkUrl = null;
Expand Down Expand Up @@ -159,12 +160,12 @@ public function setRouteParameters(?array $routeParameters): void
$this->routeParameters = $routeParameters;
}

public function getPermission(): ?string
public function getPermission(): string|Expression|null
{
return $this->permission;
}

public function setPermission(?string $permission): void
public function setPermission(string|Expression|null $permission): void
{
$this->permission = $permission;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Factory/EntityFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Event\AfterEntityBuiltEvent;
use EasyCorp\Bundle\EasyAdminBundle\Exception\EntityNotFoundException;
use EasyCorp\Bundle\EasyAdminBundle\Security\Permission;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

Expand Down Expand Up @@ -66,7 +67,7 @@ public function processActionsForAll(EntityCollection $entities, ActionConfigDto
return $this->actionFactory->processGlobalActions($actionConfigDto);
}

public function create(string $entityFqcn, $entityId = null, ?string $entityPermission = null): EntityDto
public function create(string $entityFqcn, $entityId = null, string|Expression|null $entityPermission = null): EntityDto
{
return $this->doCreate($entityFqcn, $entityId, $entityPermission);
}
Expand Down Expand Up @@ -109,7 +110,7 @@ public function getEntityMetadata(string $entityFqcn): ClassMetadata
return $entityMetadata;
}

private function doCreate(?string $entityFqcn = null, $entityId = null, ?string $entityPermission = null, $entityInstance = null): EntityDto
private function doCreate(?string $entityFqcn = null, $entityId = null, string|Expression|null $entityPermission = null, $entityInstance = null): EntityDto
{
if (null === $entityInstance && null !== $entityFqcn) {
$entityInstance = null === $entityId ? null : $this->getEntityInstance($entityFqcn, $entityId);
Expand Down
3 changes: 2 additions & 1 deletion src/Field/FieldTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use EasyCorp\Bundle\EasyAdminBundle\Config\Option\TextAlign;
use EasyCorp\Bundle\EasyAdminBundle\Dto\AssetDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Contracts\Translation\TranslatableInterface;

/**
Expand Down Expand Up @@ -165,7 +166,7 @@ public function setSortable(bool $isSortable): self
return $this;
}

public function setPermission(string $permission): self
public function setPermission(string|Expression $permission): self
{
$this->dto->setPermission($permission);

Expand Down

0 comments on commit 0b985d7

Please sign in to comment.