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
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
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;
}
}
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.
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);
}
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');
}
}
}
}