Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Role Hierarchy

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

Our site will eventually have many different sections that will need to be accessed by many different types of aquanaut users.

Maybe the genus admin section should only be visible to marine biologists and super admins, while only the "management aquanauts" can see some future edit user section. Oh, and and of course, we programmers should be able to see everything... because let's face it - we always give ourselves access.

What's the best way to organize this? When you protect a section, instead of checking for something like ROLE_ADMIN or ROLE_MANAGEMENT - which describes the type of person that will have access, you might instead use a role that describes what is being accessed. In this case, we could use something like ROLE_MANAGE_GENUS:

... lines 1 - 11
/**
* @Security("is_granted('ROLE_MANAGE_GENUS')")
* @Route("/admin")
*/
class GenusAdminController extends Controller
... lines 17 - 86

Why? The advantage is that you can very quickly give this role to any user in the database in order to allow them access to this section, but no others. If you plan ahead and do this, it'll give you more flexibility in the future.

A lot of Roles: A lot of Management

It's the perfect setup! Until you put it into practice. Because now, when you launch a new section that requires a new role - ROLE_OCTOPUS_PHOTO_MANAGE - the super admins and programmers won't have access to it until you manually add this role to all of those users. That's lame.

Of course, you can solve this with that group system we talked about earlier, but that's usually overkill. And, there's a simpler way.

Role Hierarchy

In security.yml, let's take advantage of something called role hierarchies. It's simple, it's awesome!. Add a new key called role_hierarchy and, below that, set ROLE_ADMIN: [ROLE_MANAGE_GENUS]:

... lines 1 - 2
security:
... lines 4 - 6
role_hierarchy:
ROLE_ADMIN: [ROLE_MANAGE_GENUS]
... lines 9 - 39

In other words, if anybody has ROLE_ADMIN, automatically give them ROLE_MANAGE_GENUS. Later, when you launch the new Octopus photo admin area, just add ROLE_OCTOPUS_PHOTO_MANAGE here and be done with it.

To see it in action, comment it out temporarily. Now, head to /admin/genus. Access denied! No surprise. Uncomment the role hierarchy and try it again. Access granted!

The strategy is this: first: lock down different sections using role names that describe what it's protecting - like ROLE_OCTOPUS_PHOTO_MANAGE. Second, in security.yml, create group-based roles here - like ROLE_MARINE_BIOLOGIST or ROLE_MANAGEMENT - and assign each the permissions they should have. With this setup, you should be able to give most users just one role in the database.

Of course, don't bother doing anything of this if your app is simple and will have just one or two different types of users.

Ok, now that we know how to give each user the exact access they need, let's find out how to impersonate them.

Leave a comment!

11
Login or Register to join the conversation
Default user avatar

Interesting find... roles defined in security.yml:
```

role_hierarchy:
ROLE_ADMIN: [ROLE_MANAGE_GENUS]
ROLE_ADMIN: [ACCESS_MANAGE_GENUS]
```

ROLE_MANAGE_GENUS will work with Annotations, but ACCESS_MANAGE_GENUS will not.

Reply

Hey Tony,

Hm, probably it will be vice versa, because you overwrite "ROLE_ADMIN: [ROLE_MANAGE_GENUS]" line with "ROLE_ADMIN: [ACCESS_MANAGE_GENUS]", but I can be wrong. Anyways, do you find this in our downloaded code? Because I can't find it in finish/ directory, we have only the "ROLE_ADMIN: [ROLE_MANAGE_GENUS, ROLE_ALLOWED_TO_SWITCH]" line there.

Cheers!

Reply
Default user avatar

Hi,

If I understand correctly each ROLE ei: ROLE_ADMIN will have access to certain ROLE_MANAGE ei: ROLE_MANAGE_BAR so if this ei: ROLE_ADMIN as access to more than one ROLE_MANAGE ei: ROLE_MANAGE_FOO it would look like:

role_hierarchy:
ROLE_ADMIN: [ROLE_MANAGE_FOO][ROLE_MANAGE_BAR
?

Reply
Default user avatar

Sorry all the questions are answered on the next tuto.

Thanks

Reply

Haha, no problem! I'm glad you found answer more quickly than I asked :)

Good work!

Reply
Default user avatar

What I am struggling with is how do I manage access to a resource a user owns. Classic blog example: a user can create posts and this user is the only user that can edit it. So, I want add protection to the "editAction" (with route "/posts/{id}") controller method to block access if the currently authenticated user does not own the post with given id. Of course I can't just create a role like 'EDIT_POSTS' because it doesn't specify which posts.

A solution I thought of: return a role for each post the user owns, e.g.: ROLE_EDIT_POST_3, ROLE_EDIT_POST_8, ROLE_EDIT_POST_12 etc. Then in the annotation or if that's not possible in the method itself I could maybe refer to the {id} field to check whether the currently authenticated user has the right role. I'm not sure if I like this solution as getRoles() will probably return a huge array or roles and I think it will just be a mess...

Any thoughts?

Reply
Default user avatar

I did some more research and I actually think I found my answer in the symfony docs: voters or ACL's. I think that's exactly what I need :)

Reply

Hey Johan!

Yep, you found the answer - and I'm not surprised you were confused - it's just not a topic that we talk about in this tutorial (I just casually mention voters). But don't go for ACL's - these are almost always overkill and very difficult to use. You only need ACL if you need some admin user to be able to - at random - select any object in your system (e.g. a blog) and any user in the system, and apply some permissions for that (e.g. the ability to edit). That's almost never the case, and isn't the case for you either. Usually, you already have the relationships in the database (e.g. I *own* this resource and have a relationship setup that shows that), and that's where voters are the perfect solution to write some business logic to enforce security by using the database relationship you already have.

We have 2 spots on voters:

A) https://knpuniversity.com/s... (up to date, but not as in-depth)
B) https://knpuniversity.com/s... (uses the old AbstractVoter class, which has changed slightly. But is more in-depth)

Cheers!

1 Reply
Default user avatar

That's exactly what I concluded after reading those two chapters in the Symfony docs: with voters I can do exactly what I want to do. Awesome :)

Reply
Default user avatar

Question; is there some kind of console command (like ./bin/console debug:router to display all routes) that shows all active security/role/user settings?
Especially with annotations (which are very cool) it's easy to lose track of things.

Reply

Hey Lampje,

Nope, Symfony doesn't has such feature out-of-the-box. Probably there's a bundle for that, but I don't now such, sorry. If you don't find any bundle, I think you could try to write it by yourself - Symfony allows you to create a custom console commands, but most likely you know it yourself.

P.S. But probably Symfony web debug toolbar is something that could help you with this issue.

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}
userVoice