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!

0
Login or Register to join the conversation
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "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
    }
}