使用选民保护实体

时间:2014-07-24 22:48:02

标签: php security symfony

我有三个实体:用户商店类别用户商店具有双向关系,商店商品具有双向关系。 每个用户可以创建许多商店,他可以为每个商店创建许多类别。 我已经设法使用选民保护商店,用户只能访问他的商店。

这是商店的路线

dashboard_store_view:
path:     /{id}/view
defaults: { _controller: ProjectStoreBundle:StoreDashboard:view }

网址就像这样

http://localhost/project/web/app_dev.php/dashboard/store/1/view

这是控制器 StoreDashboardController.php

<?php
//..................
    public function viewAction(Store $store)
{
    // keep in mind, this will call all registered security voters
    if (false === $this->get('security.context')->isGranted('view', $store)) {
        throw new AccessDeniedException('Unauthorised access!');
    }       
    $em = $this->getDoctrine()->getManager();

    $store = $em->getRepository('ProjectStoreBundle:Store')->findOneById($store);

    return $this->render('ProjectDashboardBundle:Store:view.html.twig',
    array(
        'store' => $store 
    ));
}

这是 StoreVoter

    <?php

namespace Project\StoreBundle\Security\Authorization\Voter;

use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class StoreVoter implements VoterInterface
{
const VIEW = 'view';
const EDIT = 'edit';
const DELETE = 'delete';

public function supportsAttribute($attribute)
{
    return in_array($attribute, array(
        self::VIEW,
        self::EDIT,
        self::DELETE,
    ));
}

public function supportsClass($class)
{
    $supportedClass = 'Project\StoreBundle\Entity\Store';

    return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}

/**
 * @var \Project\StoreBundle\Entity\Store $store
 */
public function vote(TokenInterface $token, $store, array $attributes)
{
    // check if class of this object is supported by this voter
    if (!$this->supportsClass(get_class($store))) {
        return VoterInterface::ACCESS_ABSTAIN;
    }

    // check if the voter is used correct, only allow one attribute
    // this isn't a requirement, it's just one easy way for you to
    // design your voter
    if(1 !== count($attributes)) {
        throw new InvalidArgumentException(
            'Only one attribute is allowed for VIEW or EDIT'
        );
    }

    // set the attribute to check against
    $attribute = $attributes[0];

    // get current logged in user
    $user = $token->getUser();

    // check if the given attribute is covered by this voter
    if (!$this->supportsAttribute($attribute)) {
        return VoterInterface::ACCESS_ABSTAIN;
    }

    // make sure there is a user object (i.e. that the user is logged in)
    if (!$user instanceof UserInterface) {
        return VoterInterface::ACCESS_DENIED;
    }

    switch($attribute) {
        case 'view':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $store->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;
        case 'edit':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $store->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;
        case 'delete':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $store->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;

    }
}
}

我尝试对类别做同样的事情,但我没有将每个类别保护到他自己的商店,所以evry用户可以编辑任何类别

这是路线

dashboard_category_edit:
pattern:  /{store_id}/edit/{id}
defaults: { _controller: ProjectStoreBundle:CategoryDashboard:edit }

网址就像这样

http://localhost/project/web/app_dev.php/dashboard/categories/store/1/edit/3

CategoryDashboardController.php

    public function editAction(Category $category, Store $store)
{       
    // keep in mind, this will call all registered security voters
    if (false === $this->get('security.context')->isGranted('edit', $store)) {
        throw new AccessDeniedException('Unauthorised access!');
    }

    $form = $this->createForm(new CategoryEditType(), $category);

    $request = $this->getRequest();

    if ($request->getMethod() == 'POST')
    {
        $form->bind($request);

        if ($form->isValid())
        {
            $em = $this->getDoctrine()->getManager();   
            $em->persist($category);
            $em->flush();

            $this->get('session')->getFlashBag()->add('info', 'Category bien modifié');

            return $this->redirect( $this->generateUrl('dashboard_category_index', array('store_id' => $store->getId())));
        }
    }

    return $this->render('ProjectDashboardBundle:Category:edit.html.twig',
    array(
        'form' => $form->createView() ,
        'store' => $store
        ));
}

这是 CategoryVoter

<?php

namespace Project\StoreBundle\Security\Authorization\Voter;

use Symfony\Component\Security\Core\Exception\InvalidArgumentException;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class CategoryVoter implements VoterInterface
{
const VIEW = 'view';
const EDIT = 'edit';
const DELETE = 'delete';

public function supportsAttribute($attribute)
{
    return in_array($attribute, array(
        self::VIEW,
        self::EDIT,
        self::DELETE,
    ));
}

public function supportsClass($class)
{
    $supportedClass = 'Project\StoreBundle\Entity\Category';

    return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}

/**
 * @var \Project\StoreBundle\Entity\Category $category
 */
public function vote(TokenInterface $token, $category, array $attributes)
{
    // check if class of this object is supported by this voter
    if (!$this->supportsClass(get_class($category))) {
        return VoterInterface::ACCESS_ABSTAIN;
    }

    // check if the voter is used correct, only allow one attribute
    // this isn't a requirement, it's just one easy way for you to
    // design your voter
    if(1 !== count($attributes)) {
        throw new InvalidArgumentException(
            'Only one attribute is allowed for VIEW or EDIT'
        );
    }

    // set the attribute to check against
    $attribute = $attributes[0];

    // get current logged in user
    $user = $token->getUser();

    // check if the given attribute is covered by this voter
    if (!$this->supportsAttribute($attribute)) {
        return VoterInterface::ACCESS_ABSTAIN;
    }

    // make sure there is a user object (i.e. that the user is logged in)
    if (!$user instanceof UserInterface) {
        return VoterInterface::ACCESS_DENIED;
    }

    switch($attribute) {
        case 'view':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $category->getStore()->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;
        case 'edit':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $category->getStore()->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;
        case 'delete':
            // we assume that our data object has a method getUser() to
            // get the current owner user entity for this data object
            if ($user->getId() === $category->getStore()->getUser()->getId()) {
                return VoterInterface::ACCESS_GRANTED;
            }
            break;

    }
}
}

问题是类别不是针对用户进行的,而是与商店有关,那么我该如何保护呢?


我发现此解决方案正在进行验证,如果$ category-&gt; getStore&lt;&gt; $ store所以抛出 AccessDeniedException 而不使用选民,现在它可以正常工作。

    if ($category->getStore() <> $store) {
        throw new AccessDeniedException('Unauthorised access!');
    }

所以控制器将是这样的

    /**
* @ParamConverter("store", options={"mapping": {"store_id":"id"}})
*/
public function editAction(Category $category, Store $store)
{       

    if ($category->getStore() <> $store) {
        throw new AccessDeniedException('Unauthorised access!');
    }

    $form = $this->createForm(new CategoryEditType(), $category);

    $request = $this->getRequest();

    if ($request->getMethod() == 'POST')
    {
        $form->bind($request);

        if ($form->isValid())
        {
            $em = $this->getDoctrine()->getManager();   
            $em->persist($category);
            $em->flush();

            $this->get('session')->getFlashBag()->add('info', 'Category bien modifié');

            return $this->redirect( $this->generateUrl('dashboard_category_index', array('store_id' => $store->getId())));
        }
    }

    return $this->render('ProjectDashboardBundle:Category:edit.html.twig',
    array(
        'form' => $form->createView() ,
        'store' => $store
        ));
}

这是一个很好的解决方案吗?

2 个答案:

答案 0 :(得分:0)

如果每个Category只有一个Store,则在您想要修改store_id时,在路由中使用Category毫无意义。只需使用category_id并通过调用$store$category获取$store = $category->getStore();即可。更改editAction

/**
* @ParamConverter("category", options={"mapping": {"category_id":"id"}})
*/
public function editAction(Category $category)
{       
    // keep in mind, this will call all registered security voters
    if (false === $this->get('security.context')->isGranted('edit', $category)) {
        throw new AccessDeniedException('Unauthorised access!');
    }
    $store = $category->getStore();
    (...)

答案 1 :(得分:0)

我发现这个解决方案是在表类别中获取商店的ID,然后进行两次验证, 如果表类别中的id_store与商店所有者不匹配,并且表类别中的id_store与当前商店不匹配

/**
* @ParamConverter("store", options={"mapping": {"store_id":"id"}})
*/
public function editAction(Category $category, Store $store)
{       

    // get id_store in table category
    $idStore = $category->getStore(); 

    // if id_store in table category doesn't match user 
    if (false === $this->get('security.context')->isGranted('edit', $idStore)) {
        throw new AccessDeniedException('Unauthorised access!');
    }       

    // if id_store in table category doesn't match current store
    if (false === ($idStore === $store)) {
        throw new AccessDeniedException('Unauthorised access!');
    }       

    $form = $this->createForm(new CategoryEditType(), $category);

    $request = $this->getRequest();

    if ($request->getMethod() == 'POST')
    {
        $form->bind($request);

        if ($form->isValid())
        {
            $em = $this->getDoctrine()->getManager();   
            $em->persist($category);
            $em->flush();

            $this->get('session')->getFlashBag()->add('info', 'Category bien modifié');

            return $this->redirect( $this->generateUrl('dashboard_category_index', array('store_id' => $store->getId())));
        }
    }

    return $this->render('ProjectDashboardBundle:Category:edit.html.twig',
    array(
        'form' => $form->createView() ,
        'store' => $store
        ));
}