Security and Voters in Symfony 3.4

#app/config/security.yml
security:
#...
role_hierarchy:
ROLE_ARTIST: ROLE_USER
ROLE_ADMIN: [ROLE_ARTIST, ROLE_USER]
#...
#app/config/security.yml
security:
#...
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, role: ROLE_USER }
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
/**
* @Route("/")
*/
public function indexAction(Request $request)
{
if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
throw $this->createAccessDeniedException('Unable to access this page!');
}
// ...
}
}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
/**
* @Route("/")
*/
public function indexAction(Request $request)
{
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
// ...
}
}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
/**
* @Route("/")
* @Security("has_role('ROLE_ADMIN')")
*/
public function indexAction(Request $request)
{
// ...
}
// ...
}
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
// ...

/**
* @Route("/edit/{id}")
* @Security("has_role('ROLE_ARTIST')")
*/
public function editAction(Request $request, Artist $artist)
{
// ...
}
// ...
}
namespace AppBundle\Security;

use AppBundle\Entity\Artist;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;

class ArtistVoter extends Voter
{
// these strings are just invented: you can use anything
const VIEW = 'view';
const EDIT = 'edit';

private $decisionManager;

public function __construct(AccessDecisionManagerInterface $decisionManager)
{
$this->decisionManager = $decisionManager;
}

protected function supports($attribute, $subject)
{
// if the attribute isn't one we support, return false
if (!in_array($attribute, array(self::VIEW, self::EDIT))) {
return false;
}

// only vote on Artist objects inside this voter
if (!$subject instanceof Artist) {
return false;
}

return true;
}

protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();

if (!$user instanceof User) {
// the user must be logged in; if not, deny access
return false;
}

// ROLE_SUPER_ADMIN can do anything! The power!
if ($this->decisionManager->decide($token, array('ROLE_ADMIN'))) {
return true;
}

// you know $subject is a Artist object, thanks to supports
/** @var Artist $artist */
$artist = $subject;

switch ($attribute) {
case self::VIEW:
return $this->canView($artist, $user);
case self::EDIT:
return $this->canEdit($artist, $user);
}

throw new \LogicException('This code should not be reached!');
}

private function canView(Artist $artist, User $user)
{
// if they can edit, they can view
if ($this->canEdit($artist, $user)) {
return true;
}

// the Artist object could have, for example, a method isPrivate()
// that checks a boolean $private property
return $user === $artist->getUser();
}

private function canEdit(Artist $artist, User $user)
{
// this assumes that the data object has a getOwner() method
// to get the entity of the user who owns this data object
return $user === $artist->getUser();
}
}
protected function supports($attribute, $subject)
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
public function vote(TokenInterface $token, $subject, array $attributes)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
// ...

/**
* @Route("/edit/{id}")
* @Security("has_role('ROLE_ARTIST')")
*/
public function editAction(Request $request, Artist $artist)
{
$this->denyAccessUnlessGranted('edit', $artist);
// ...
}
// ...
}
$this->denyAccessUnlessGranted('edit', $artist)
namespace Symfony\Bundle\FrameworkBundle\Controller;

trait ControllerTrait
{
//...

/**
* Throws an exception unless the attributes are granted against the current authentication token and optionally
* supplied subject.
*
* @param mixed $attributes The attributes
* @param mixed $subject The subject
* @param string $message The message passed to the exception
*
* @throws AccessDeniedException
*
* @final since version 3.4
*/
protected function denyAccessUnlessGranted($attributes, $subject = null, $message = 'Access Denied.')
{
if (!$this->isGranted($attributes, $subject)) {
$exception = $this->createAccessDeniedException($message);
$exception->setAttributes($attributes);
$exception->setSubject($subject);

throw $exception;
}
}

//...
}
$this->denyAccessUnlessGranted('edit', $artist);
$this->get('security.authorization_checker')->isGranted('edit', $artist)
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

/**
* @Route("/admin/artist")
*/
class ArtistController extends Controller
{
// ...

/**
* @Route("/edit/{id}")
* @Security("has_role('ROLE_ARTIST')")
*/
public function editAction(Request $request, Artist $artist)
{
if (false === $this->get('security.authorization_checker')->isGranted('edit', $artist)) {
throw $this->createAccessDeniedException('Unable to access this page!');
}
// ...
}
// ...
}
$this->get('security.authorization_checker')->isGranted(...)
namespace Symfony\Component\Security\Core\Authorization\Voter;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* Voter is an abstract default implementation of a voter.
*
* @author Roman Marintšenko
* @author Grégoire Pineau
*/
abstract class Voter implements VoterInterface
{

/**
* {@inheritdoc}
*/
public function vote(TokenInterface $token, $subject, array $attributes)
{
// abstain vote by default in case none of the attributes are supported
$vote = self::ACCESS_ABSTAIN;

foreach ($attributes as $attribute) {
if (!$this->supports($attribute, $subject)) {
continue;
}

// as soon as at least one attribute is supported, default is to deny access
$vote = self::ACCESS_DENIED;

if ($this->voteOnAttribute($attribute, $subject, $token)) {
// grant access as soon as at least one attribute returns a positive response
return self::ACCESS_GRANTED;
}
}

return $vote;
}

//...
}

--

--

Coder, Entrepreneur, Co-founder at SlowCode

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store