Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to give security voter access to current object

I want to use a Voter to only allow owners to edit a project object in my application.

I have a route /project/42/edit that invokes my action ProjectController.editAction(Project $project). I use a type hint (Project $project) to automatically invoke the ParamConverter to convert the ID 42 from the URI into a project object. This works nicely for the controller action, however it seems to be invoked too late for the voter. Its vote() method gets called with the request as 2nd parameter, not my project.

Is there a way to pass the project to the voter without having to retrieve it from the database again?

UPDATE: I learned that I have to manually call isGranted() on the security context in the edit method. This is very similar in approach to this answer.

Here is my Voter:

namespace FUxCon2013\ProjectsBundle\Security;

use FUxCon2013\ProjectsBundle\Entity\Project;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

class OwnerVoter implements VoterInterface
{
    public function __construct(ContainerInterface $container)
    {
        $this->container     = $container;
    }

    public function supportsAttribute($attribute)
    {
        return $attribute == 'MAY_EDIT';
    }

    public function supportsClass($class)
    {
        // your voter supports all type of token classes, so return true
        return true;
    }

    function vote(TokenInterface $token, $object, array $attributes)
    {
        if (!in_array('MAY_EDIT', $attributes)) {
            return self::ACCESS_ABSTAIN;
        }
        if (!($object instanceof Project)) {
            return self::ACCESS_ABSTAIN;
        }

        $user = $token->getUser();
        $securityContext = $this->container->get('security.context');

        return $securityContext->isGranted('IS_AUTHENTICATED_FULLY')
            && $user->getId() == $object->getUser()->getId()
            ? self::ACCESS_GRANTED
            : self::ACCESS_DENIED;
    }
}

I register this in configure.yml so that it gets the service container as parameter:

services:
    fuxcon2013.security.owner_voter:
        class:      FUxCon2013\ProjectsBundle\Security\OwnerVoter
        public:     false
        arguments: [ @service_container ]
        tags:
            - { name: security.voter }

The last block is to configure the access decision manager in security.yml to unanimous:

security:
    access_decision_manager:
        # strategy can be: affirmative, unanimous or consensus
        strategy: unanimous
        allow_if_all_abstain: true
like image 916
Olav Avatar asked Jun 13 '13 13:06

Olav


1 Answers

Please have a look at this answer i have written yesterday.

You can easily adapt it to your needs by checking for the owner of your object.

like image 176
Nicolai Fröhlich Avatar answered Nov 14 '22 13:11

Nicolai Fröhlich