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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With