Voter classes in Symfony 4 - symfony

I'm taking over someone's code and I don't understand something about the voting.
Here is the PhotosController class:
class PhotosController extends Controller
{
/**
* #Route("/dashboard/photos/{id}/view", name="dashboard_photos_view")
* #Security("is_granted('view.photo', photo)")
* #param Photo $photo
* #param PhotoRepository $photoRepository
*/
public function index(Photo $photo, PhotoRepository $photoRepository)
{
$obj = $photoRepository->getFileObjectFromS3($photo);
header("Content-Type: {$obj['ContentType']}");
echo $obj['Body'];
exit;
}
Here is the voter class:
class PhotoVoter extends Voter
{
const VIEW = 'view.photo';
protected function supports($attribute, $subject)
{
if (!$subject instanceof Photo) {
return false;
}
if (!in_array($attribute, array(self::VIEW))) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $subject->getUser()->getId() === $token->getUser()->getId();
}
}
I don't understand what the
, photo
is for in the PhotosController class. And in PhpStorm I get "cannot find declaration" when I try to go to the "is_granted" declaration.

Related

#Security("is_granted('remove', user)") uses not request user, symfony2

When I use the annotation #Security("is_granted('remove', user)"), I get a wrong user in Voter
/**
* Delete User by ID
*
* #Rest\Delete("/{id}", name="delete_user")
* #Security("is_granted('remove', user)")
*
* #ApiDoc(
* section="5. Users",
* resource=true,
* description="Delete User",
* headers={
* {
* "name"="Authorization: Bearer [ACCESS_TOKEN]",
* "description"="Authorization key",
* "required"=true
* }
* },
* requirements={
* {
* "name"="id",
* "dataType"="string",
* "requirement"="\[a-z\-]+",
* "description"="Id of the object to receive"
* }
* },
* output="Status"
* )
*
* #param User $user
* #return Response;
*/
public function deleteAction(User $user)
{
//$this->denyAccessUnlessGranted('remove', $user);
$em = $this->getDoctrine()->getManager();
$em->remove($user);
$em->flush();
$view = $this->view('Success deleted', Response::HTTP_NO_CONTENT);
return $this->handleView($view);
}
But, if I use functions in the body $this->denyAccessUnlessGranted('remove', $user);, it's all right. Help me to understand...
Settings
services:
user.user_voter:
class: OD\UserBundle\Security\UserVoter
arguments: ['#security.access.decision_manager']
public: false
tags:
- { name: security.voter }
Voter
namespace OD\UserBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use OD\UserBundle\Entity\User;
class UserVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
const REMOVE = 'remove';
protected function supports($attribute, $subject)
{
# if the attribute isn't one we support, return false
if (!in_array($attribute, array(self::VIEW, self::EDIT, self::REMOVE))) {
return false;
}
# only vote on Booking objects inside this voter
if (!$subject instanceof User) {
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;
}
switch ($attribute) {
case self::VIEW:
return $this->canView($subject, $user);
case self::EDIT:
return $this->canEdit($subject, $user);
case self::REMOVE:
return $this->canRemove($subject, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canView(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canEdit(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canRemove(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
}
* #Security("is_granted('remove', removingUser)")
* #param User $removingUser
* #return Response;
*/
public function deleteAction(User $removingUser)
This code works well. This code works well. I did not find the confirmation, but it seems the user is reserved for the current user

Instructions for migrate legacy Knp-Menu code to version 2.0

I need to migrate a really old version of Knp Menu to a newest one. The real problem is here
$collapse = new CollapseItem($group,$router->generate('seguridad_group_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$collapse->setIcon('sp sp-ico-menu-grupo sp-icon-display');
$this->addChild($collapse);
How can I make it following the menu-as-service-way in the version 2 of KnpMenu?
The rest of code is this
The menu service implementation:
namespace Primicia\SeguridadBundle\Menu;
use Knp\Bundle\MenuBundle\Menu;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Knp\Bundle\MenuBundle\Renderer\RendererInterface;
class SeguridadMenu extends Menu {
/**
* #param Request $request
* #param Router $router
* #param Container $container
*/
public function __construct(Request $request, Router $router, $container)
{
parent::__construct();
$this->setCurrentUri($request->getRequestUri());
$this->setAttribute('class','nav nav-list menu_lateral');
$translator = $container->get('translator');
if($container->get('security.context')->isGranted('ROLE_ADMIN'))
{
$user = $translator->trans('menu.user.titles',array(),'SeguridadBundle');
$signal = new CollapseItem($user,$router->generate('seguridad_user_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$signal->setIcon('sp sp-ico-menu-usuario sp-icon-display');
$this->addChild($signal);
$group = $translator->trans('menu.group.titles',array(),'SeguridadBundle');
$collapse = new CollapseItem($group,$router->generate('seguridad_group_list'),array('class'=>'submenu'),'Primicia\SeguridadBundle\Menu\CollapseItem');
$collapse->setIcon('sp sp-ico-menu-grupo sp-icon-display');
$this->addChild($collapse);
}
}
/**
* Gets renderer which is used to render menu items.
*
* #return RendererInterface $renderer Renderer.
*/
public function getRenderer()
{
if(null === $this->renderer) {
if($this->isRoot()) {
$this->setRenderer(new ApcRenderer());
}
else {
return $this->getParent()->getRenderer();
}
}
return $this->renderer;
}
}
The CollapseItem class, which is used in the $router->generate
namespace Primicia\SeguridadBundle\Menu;
use Knp\Menu\MenuItem;
class CollapseItem extends MenuItem
{
protected $hasIcon;
public function renderLink()
{
$label = $this->renderLabel();
$uri = $this->getUri();
if (!$uri) {
die;
return sprintf('<a class="dropdown-toggle" href="#">%s</a>', $label);
}
return sprintf('<a class="dropdown-toggle" href="%s">%s</a>', $uri, $label);
}
public function setIcon($icon)
{
$this->hasIcon=$icon;
return $this;
}
public function getIcon()
{
return $this->hasIcon;
}
}

Symfony2: Call Voter from another Voter

I am using Voters to restrict access to entities in a REST API.
Step 1
Consider this voter that restricts users access to blog posts:
class BlogPostVoter extends Voter
{
public function __construct(AccessDecisionManagerInterface $decisionManager)
{
$this->decisionManager = $decisionManager;
}
/**
* Determines if the attribute and subject are supported by this voter.
*
* #param string $attribute An attribute
* #param int $subject The subject to secure, e.g. an object the user wants to access or any other PHP type
*
* #return bool True if the attribute and subject are supported, false otherwise
*/
protected function supports($attribute, $subject)
{
if (!in_array($attribute, $this->allowedAttributes)) {
return false;
}
if (!$subject instanceof BlogPost) {
return false;
}
return true;
}
/**
* Perform a single access check operation on a given attribute, subject and token.
*
* #param string $attribute
* #param mixed $subject
* #param TokenInterface $token
* #return bool
* #throws \Exception
*/
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $this->canUserAccess($attribute, $subject, $token);
}
public function canUserAccess($attribute, $subject, TokenInterface $token) {
if ($this->decisionManager->decide($token, array('ROLE_SUPPORT', 'ROLE_ADMIN'))) {
return true;
}
//other logic here omitted ...
return false;
}
}
You can see there is a public function canUserAccess to determine if the user is allowed to see the BlogPost. This all works just fine.
Step 2
Now I have another voter that checks something else, but also needs to check this same exact logic for BlogPosts. My thought was to:
add a new voter
perform some other checks
but then also perform this BlogPost check
So I thought I would inject the BlogPostVoter into my other voter like this:
class SomeOtherVoter extends Voter
{
public function __construct(BlogPostVoter $blogPostVoter)
{
$this->decisionManager = $decisionManager;
}
...
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
//other logic
if ($this->blogPostVoter->canUserAccess($attribute, $subject, $token)) {
return true;
}
return false;
}
}
Problem
When I do this I get the following error, using both setter and constructor injection:
Circular reference detected for service "security.access.decision_manager", path: "security.access.decision_manager"
I don't see where the security.access.decision_manager should depend on the Voter implementations. So I'm not seeing where the circular reference is.
Is there another way I can call VoterA from VoterB?
To reference VoterOne from VoterTwo you can inject the AuthorizationCheckerInterface into VoterTwo and then call ->isGranted('ONE'). Where ONE is the supported attribute of VoterOne.
Like:
class VoterTwo extends Voter
{
private $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
protected function supports($attribute, $subject)
{
return in_array($attribute, ['TWO']);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $this->authorizationChecker->isGranted('ONE', $subject);
}
}
In this example VoterTwo does just redirect the request to VoterOne (or the voter that supports the attribute ONE). This can then be extended through additional conditions.

How to edit the upload file field while updating the value in sonata admin bundle?

while i upload the file from sonata admin bundle the file is uploaded but while i edit the file the file field also ask for the upload the new file.. please help me for the edit option for the file upload.
class Adds {
use Symfony\Component\Config\Definition\IntegerNode;
use Doctrine\Common\Collections\ArrayCollection;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
const SERVER_PATH_TO_IMAGE_FOLDER = 'uploads/';
..........
/**
* #var string
*
* #ORM\Column(name="file", type="string", length=255)
* #Assert\File(maxSize="5000000")
*/
private $file;
public function getFile() {
return $this->file;
}
public function setFile($file) {
$this->file = $file;
}
,............
public function upload() {
if (null === $this->getFile()) {
return;
}
$this->getFile()->move(
self::SERVER_PATH_TO_IMAGE_FOLDER, $this->getFile()->getClientOriginalName());
$this->filename = $this->getFile()->getClientOriginalName();
$this->setFile(null);
}
/**
* upload the file to the server
*/
public function lifecycleFileUpload() {
$this->upload();
}
/**
* Updates the hash value to force the preUpdate and postUpdate events to fire
*/
public function refreshUpdated($file) {
if (null === $file->getFile()) {
return;
}
$file->getFile()->move(
self::SERVER_PATH_TO_IMAGE_FOLDER, date('YmdHis') . $file->getFile()->getClientOriginalName());
$file->filename = $file->getFile()->getClientOriginalName();
$file->filename = date('YmdHis') . $file->filename;
$this->setFile($file->filename);
}
?>
And the adAdmin file is
<?php
...............
................
class AddsAdmin extends Admin {
............
protected function configureFormFields(FormMapper $formMapper) {
$formMapper->add('file', 'file', array('label' => 'File :', 'data_class' => null))
->end();
}
public function prePersist($ad) {
$this->manageFileUpload($ad);
if ($this->userLevel == Organization::LEVEL_CLIENT) {
$user = $this->securityContext->getToken()->getUser();
$ad->setOrganization($user->getOrganization());
}
}
public function preUpdate($ad) {
$this->manageFileUpload($ad);
}
private function manageFileUpload($ad) {
if ($ad->getFile()) {
$ad->refreshUpdated($ad);
}
}
Just check the type of field;
SO for preUpdate/prePersist call your handle:
public function preUpdate($object)
{
$this->handleUploads($object);
}
public function prePersist($object)
{
$this->handleUploads($object);
}
and in handle just check:
if ($file->getFile() instanceof UploadedFile && $file->getFile()->isValid()) {
$targetFilename = md5(rand() . time()) . '.' . $file->getFile()->getClientOriginalExtension();
$file->getFile()->move($uploadDirectory, $targetFilename);
$file->setFile('uploads/' . $targetFilename);
}

Finding out what changed via postUpdate listener in Symfony 2.1

I have a postUpdate listener and I'd like to know what the values were prior to the update and what the values for the DB entry were after the update. Is there a way to do this in Symfony 2.1? I've looked at what's stored in getUnitOfWork() but it's empty since the update has already taken place.
You can use this ansfer Symfony2 - Doctrine - no changeset in post update
/**
* #param LifecycleEventArgs $args
*/
public function postUpdate(LifecycleEventArgs $args)
{
$changeArray = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($args->getObject());
}
Found the solution here. What I needed was actually part of preUpdate(). I needed to call getEntityChangeSet() on the LifecycleEventArgs.
My code:
public function preUpdate(Event\LifecycleEventArgs $eventArgs)
{
$changeArray = $eventArgs->getEntityChangeSet();
//do stuff with the change array
}
Your Entitiy:
/**
* Order
*
* #ORM\Table(name="order")
* #ORM\Entity()
* #ORM\EntityListeners(
* {"\EventListeners\OrderListener"}
* )
*/
class Order
{
...
Your listener:
class OrderListener
{
protected $needsFlush = false;
protected $fields = false;
public function preUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
return $this->setFields($entity, $eventArgs);
}
public function postUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
foreach ($this->fields as $field => $detail) {
echo $field. ' was ' . $detail[0]
. ' and is now ' . $detail[1];
//this is where you would save something
}
$eventArgs->getEntityManager()->flush();
return true;
}
public function setFields($entity, LifecycleEventArgs $eventArgs)
{
$this->fields = array_diff_key(
$eventArgs->getEntityChangeSet(),
[ 'modified'=>0 ]
);
return true;
}
public function isCorrectObject($entity)
{
return $entity instanceof Order;
}
}
You can find example in doctrine documentation.
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}

Resources