Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Restricting Access to an Entire Crud Section

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

So... great! We can now restrict things on an action-by-action basis. But sometimes... it's not that complicated! Sometimes you just want to say:

I want to require ROLE_MODERATOR to be able to access any part of a CRUD section as a whole.

In that case, instead of trying to set permissions on every action like this, you can be lazy and use normal security.

For example, head to the top of QuestionCrudController. Above the class, leverage the #[IsGranted] attribute from SensioFrameworkExtraBundle. Just for a minute, let's pretend that we're going to require ROLE_SUPER_ADMIN to use any part of this section.

... lines 1 - 15
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
#[IsGranted('ROLE_SUPER_ADMIN')]
class QuestionCrudController extends AbstractCrudController
... lines 20 - 93

If we move over now and refresh... "Access Denied"! Yea, since these controllers are real controllers, just about everything that works in a normal controller also works inside of these CRUD controllers.

Let's undo that. Or, if you want, we can put ROLE_MODERATOR up there to make sure that if we missed any actions, users will at least need to have ROLE_MODERATOR. Since we're already logged in with that user, now... we're good!

... lines 1 - 17
#[IsGranted('ROLE_MODERATOR')]
class QuestionCrudController extends AbstractCrudController
... lines 20 - 93

One thing I do want to point out is that, when you link to something, you do need to keep the permissions on that link "in sync" with the permissions for the controller you're linking to.

For example, let's temporarily remove the link permission for the menu item.

... lines 1 - 24
class DashboardController extends AbstractDashboardController
{
... lines 27 - 57
public function configureMenuItems(): iterable
{
... line 60
yield MenuItem::linkToCrud('Questions', 'fa fa-question-circle', Question::class);
//->setPermission('ROLE_MODERATOR');
... lines 63 - 66
}
... lines 68 - 127
}

Then, in QuestionCrudController, down on index, temporarily require ROLE_SUPER_ADMIN. This means that we should not have access.

... lines 1 - 18
class QuestionCrudController extends AbstractCrudController
{
... lines 21 - 34
public function configureActions(Actions $actions): Actions
{
return parent::configureActions($actions)
->setPermission(Action::INDEX, 'ROLE_SUPER_ADMIN')
... lines 39 - 43
}
... lines 45 - 91
}

And if we move over and refresh... that's true! We're denied access! But go back to /admin. Uh oh: the Questions link does show up. EasyAdmin isn't smart enough to realize that if we clicked this, we wouldn't have access. It's our responsibility to make sure that the permissions on our link are set up correctly.

Go change this back to ROLE_MODERATOR... and over here, we'll restore that permission. Now we're good. Our question section requires ROLE_MODERATOR and specific actions inside of it, like DELETE, require ROLE_SUPER_ADMIN.

Nice work team!

But security can go even further! Next let's hide individual fields based on permissions and even hide specific entity results based on which admin user is logged in. Whoa...

Leave a comment!

2
Login or Register to join the conversation

Hi! How can I see the list of predefined roles? And how can I create my own roles, for example, ROLE_EDITOR?
Thank you.

Reply

Yo @aYo @Ruslan-A!

There actually are NO predefined roles. Well, ROLE_USER is "kind of" a predefined role. When you run make:user, it generates a User class where the getRoles() method ALWAYS return at least ROLE_USER. So, by convention, in Symfony, we tend to use ROLE_USER as an easy way to simple check "if the user is logged in".

Beyond that, you're free to make up whatever roles you want. They're not registered anywhere. For example, to add ROLE_EDITOR, you would do two things:

A) Use it somewhere: like in an EA controller, add #[IsGranted('ROLE_EDITOR')]. At this point, Symfony will start to require the user to have that role to see that controller. But no users will have that roll yet.
B) Allow the user to be given this role. For example, if you have a UserCrudController, you will configure a roles field where you create a list of all of the roles a user could have: you can literally create an array of whatever roles your system has right here. Then, the idea is that this UserCrudController would be used by super admins (generally we use ROLE_SUPER_ADMIN for these users - but that is just a convention): they would edit a user, check which roles that user should have and done!

Let me know if this helps :)

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0
    }
}
userVoice