Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement role / resources ACL in Symfony2

Tags:

symfony

acl

I'm a bit disconcerted by the way access control lists are implemented in Symfony2.

In Zend Framework (versions 1 & 2), a list of resources and a list of roles are defined and each role is assigned a subset of resources it's allowed to access. Resources and roles are therefore the main vocabulary of ACL implementation, which is not the case in Symfony2, where only roles rule.

In a legacy app database, I have tables defining a list of roles, a list of resources and a list of allowed resources for each role (many-to-many relationship). Each user is assigned a role (admin, super admin, editor, and such).

I need to make use of this database in a Symfony2 application. My resources look like this : ARTICLE_EDIT, ARTICLE_WRITE, COMMENT_EDIT, etc.

My User entity in Symfony implements the Symfony\Component\Security\Core\User\UserInterface interface and therefore has a getRoles) method.

I intend to use this method to define the allowed resources, which means I use roles as resources (I mean that what's called resources in Zend Framework is called roles here).

Do you confirm that I should use this method ?

This means I don't care anymore about the role (admin, editor, ...) of each user, but only about its resources.

I would then use $this->get('security.context')->isGranted('ROLE_ARTICLE_WRITE') in my controllers.

Is this the right way to do it and wouldn't it be a circumvented way to use roles in Symfony?

like image 223
Michaël Perrin Avatar asked Jan 07 '13 10:01

Michaël Perrin


1 Answers

To answer this question years later, it was pretty easy to solve.

The solution is to mix the notions of roles and resources.

Let's assume a role table, a resource table and and role_resource many to many relation are defined.

Users are stored in a user table.

Here are the corresponding Doctrine entities:

User:

use Symfony\Component\Security\Core\User\UserInterface;

class User implements UserInterface
{
    /**
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /**
     * @ManyToOne(targetEntity="Role")
     * @JoinColumn(name="role_id", referencedColumnName="id")
     **/
    private $role;

    // ...
}

Role:

class Role
{
    /**
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /** @Column(type="string") */
    private $name;

    /**
     * @ManyToMany(targetEntity="Resource")
     * @JoinTable(name="role_resource",
     *      joinColumns={@JoinColumn(name="role_id", referencedColumnName="id")},
     *      inverseJoinColumns={@JoinColumn(name="resource_id", referencedColumnName="id")}
     *      )
     **/
    private $resources;

    // ...
}

Resource:

class Resource
{
    /**
     * @Id @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /** @Column(type="string") */
    private $name;

    // ...
}

So now the solution is to implement the getRoles of UserInterface this way:

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Role\Role;

class User implements UserInterface
{
    // ...

    /**
     * @var Role[]
     **/
    private $roles;

    /**
     * {@inheritDoc}
     */
    public function getRoles()
    {
        if (isset($this->roles)) {
            return $this->roles;
        }

        $this->roles = array();

        $userRole = $this->getRole();

        $resources = $userRole->getResources();

        foreach ($resources as $resource) {
            $this->roles[] = new Role('ROLE_' . $resource);
        }

        return $this->roles;
    }

}

This way, resources attributed to the current user can be checked this way (considering there is a resource whose name is ARTICLE_WRITE):

$this->get('security.context')->isGranted('ROLE_ARTICLE_WRITE')
like image 108
Michaël Perrin Avatar answered Jan 04 '23 14:01

Michaël Perrin