Entity & Field Permissions
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeMost of the time, securing your admin will probably mean denying access to entire sections or specific actions based on a role. But we can go a lot further.
Hiding a Field for some Admins
Let's imagine that, for some reason, the number of votes is a sensitive number that should only be displayed and modified by super admins. Head over to QuestionCrudController
. This is something we can control on each field, so find VotesField
. Here it is. Add ->setPermission()
and then pass ROLE_SUPER_ADMIN
.
// ... lines 1 - 18 | |
class QuestionCrudController extends AbstractCrudController | |
{ | |
// ... lines 21 - 45 | |
public function configureFields(string $pageName): iterable | |
{ | |
// ... lines 48 - 70 | |
yield VotesField::new('votes', 'Total Votes') | |
// ... line 72 | |
->setPermission('ROLE_SUPER_ADMIN'); | |
// ... lines 74 - 91 | |
} | |
} |
I'm currently logged in as "moderatoradmin", so I'm not a super admin. And so, when I refresh, it's as simple as that! The votes field disappears, both on the list page and on the edit page. Super cool!
Hiding some Results for some Admins
Ok, let's try something different. What if we want to show only some items in an admin section based on the user? Maybe, for some reason, my user can only see certain questions.
Or, here's a better example. I'm currently logged in as a moderator, whose job is to approve questions. If we click the Users section, a moderator probably shouldn't be able to see and edit other user accounts. We could hide the section entirely for moderators, or we could add some security so that only their own user account is visible to them. This is called "entity permissions". It answers the question of whether or not to show a specific row in an admin section based on the current user. And we control this on the CRUD level: we set an entity permission for an entire CRUD section.
Head over to UserCrudController
and, at the bottom, override the configureCrud()
method. And now, for this entire CRUD, we can say ->setEntityPermission()
and pass ADMIN_USER_EDIT
.
// ... lines 1 - 5 | |
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; | |
// ... lines 7 - 17 | |
class UserCrudController extends AbstractCrudController | |
{ | |
// ... lines 20 - 24 | |
public function configureCrud(Crud $crud): Crud | |
{ | |
return parent::configureCrud($crud) | |
->setEntityPermission('ADMIN_USER_EDIT'); | |
} | |
// ... lines 30 - 63 | |
} |
Notice this is not a role. EasyAdmin calls the security system for each entity that it's about to display and passes this ADMIN_USER_EDIT
string into the security system. If we used a role here - like ROLE_SUPER_ADMIN
- that would return true or false for every item. It would end up showing either all the items or none of them.
Nope, a role won't work here. So, instead, I'm passing this ADMIN_USER_EDIT
string, which is something I totally just invented. In a few minutes, we're going to create a custom voter to handle that.
But since we haven't created that voter yet, this will return false in the security system in all cases. In other words, if this is working correctly, we won't see any items in this list.
Entity Permissions and formatValue()
Let's try it! Refresh and... okay. We don't see any items in the list, but it's because we have a gigantic error. It's coming from UserCrudController
: the formatValue()
callback on the avatar field:
Argument #2 ($user) must be of type
App\Entity\User
, null given
This error originates in a configurator. Go look at that field. Let's see... avatar... here it is. You might remember that formatValue()
is the way we control how a value is rendered on the index and detail pages. And it's simple: it passes us the current User
object - since we're in the UserCrudController
and rendering users - and then we return whatever value we want.
But, when you use entity permissions, it's possible that this User
object will be null
because this is a row that won't be displayed. I'm not sure exactly why EasyAdmin calls our callback... even though the row is about to be hidden, but it does. So it means that we need to allow this to be null
. I'll add a question mark to make it nullable.
And then, because we're using PHP 8, we can be super trendy by using a new syntax: $user?->getAvatarUrl()
. That says that if there is a user, call ->getAvatarUrl()
and return it. Else, just return null
.
// ... lines 1 - 30 | |
public function configureFields(string $pageName): iterable | |
{ | |
// ... lines 33 - 34 | |
yield AvatarField::new('avatar') | |
->formatValue(static function ($value, ?User $user) { | |
return $user?->getAvatarUrl(); | |
}) | |
// ... lines 39 - 62 | |
} | |
// ... lines 64 - 65 |
There's one other place that we need to do this. It's in QuestionCrudController
, down here on the askedBy
field. Add a question mark, and then another question mark right in the middle of $question?->getAskedBy()
.
// ... lines 1 - 18 | |
class QuestionCrudController extends AbstractCrudController | |
{ | |
// ... lines 21 - 45 | |
public function configureFields(string $pageName): iterable | |
{ | |
// ... lines 48 - 73 | |
yield AssociationField::new('askedBy') | |
// ... line 75 | |
->formatValue(static function ($value, ?Question $question): ?string { | |
if (!$user = $question?->getAskedBy()) { | |
return null; | |
} | |
// ... lines 80 - 81 | |
}) | |
// ... lines 83 - 91 | |
} | |
} |
Go refresh again and... beautiful! No results are showing, and we get this nice message:
Some results can't be displayed because you don't have enough permissions.
Woo! And of course, if we tried to search for something, that would also take into account our permissions.
Next, let's create the voter so that we can deny access exactly when we want to and ultimately show only our user record when a moderator is in the Users section.
Hey there,
Maybe I'm just tired, but I can't find a way to add custom logic on a field-based permission.
For example, I want only the user itself or an admin to be able to modify the password field in my UserCrudController.
I would need something like this but it doesn't work because it's not passing the user as the subject.
Or something like this, but it doesn't work because
setPermission
only accepts a string.I'm kind of stuck. I'll go sleep on it.
Cheers!