diff --git a/app/Resources/views/blog/post_show.html.twig b/app/Resources/views/blog/post_show.html.twig index d1cbf36f0..37e9dc2d9 100644 --- a/app/Resources/views/blog/post_show.html.twig +++ b/app/Resources/views/blog/post_show.html.twig @@ -55,7 +55,7 @@ {% endblock %} {% block sidebar %} - {% if post.isAuthor(app.user) %} + {% if is_granted('edit', post) %}
{{ 'action.edit_post'|trans }} diff --git a/app/config/services.yml b/app/config/services.yml index a3c388a99..7fedcc5d3 100644 --- a/app/config/services.yml +++ b/app/config/services.yml @@ -48,6 +48,14 @@ services: tags: - { name: kernel.event_subscriber } + # To inject the voter into the security layer, you must declare it as a service and tag it with security.voter. + # See http://symfony.com/doc/current/security/voters.html#configuring-the-voter + app.post_voter: + class: AppBundle\Security\PostVoter + public: false + tags: + - { name: security.voter } + # Uncomment the following lines to define a service for the Post Doctrine repository. # It's not mandatory to create these services, but if you use repositories a lot, # these services simplify your code: diff --git a/src/AppBundle/Controller/Admin/BlogController.php b/src/AppBundle/Controller/Admin/BlogController.php index 98042098c..14f886b9f 100644 --- a/src/AppBundle/Controller/Admin/BlogController.php +++ b/src/AppBundle/Controller/Admin/BlogController.php @@ -119,12 +119,9 @@ public function newAction(Request $request) */ public function showAction(Post $post) { - // This security check can also be performed: - // 1. Using an annotation: @Security("post.isAuthor(user)") - // 2. Using a "voter" (see http://symfony.com/doc/current/cookbook/security/voters_data_permission.html) - if (!$post->isAuthor($this->getUser())) { - throw $this->createAccessDeniedException('Posts can only be shown to their authors.'); - } + // This security check can also be performed + // using an annotation: @Security("is_granted('show', post)") + $this->denyAccessUnlessGranted('show', $post, 'Posts can only be shown to their authors.'); return $this->render('admin/blog/show.html.twig', [ 'post' => $post, @@ -139,9 +136,7 @@ public function showAction(Post $post) */ public function editAction(Post $post, Request $request) { - if (!$post->isAuthor($this->getUser())) { - throw $this->createAccessDeniedException('Posts can only be edited by their authors.'); - } + $this->denyAccessUnlessGranted('edit', $post, 'Posts can only be edited by their authors.'); $entityManager = $this->getDoctrine()->getManager(); @@ -169,11 +164,10 @@ public function editAction(Post $post, Request $request) * * @Route("/{id}/delete", name="admin_post_delete") * @Method("POST") - * @Security("post.isAuthor(user)") + * @Security("is_granted('delete', post)") * * The Security annotation value is an expression (if it evaluates to false, * the authorization mechanism will prevent the user accessing this resource). - * The isAuthor() method is defined in the AppBundle\Entity\Post entity. */ public function deleteAction(Request $request, Post $post) { diff --git a/src/AppBundle/Security/PostVoter.php b/src/AppBundle/Security/PostVoter.php new file mode 100644 index 000000000..cd2269fe7 --- /dev/null +++ b/src/AppBundle/Security/PostVoter.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace AppBundle\Security; + +use AppBundle\Entity\Post; +use AppBundle\Entity\User; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; + +/** + * It grants or denies permissions for actions related to blog posts (such as + * showing, editing and deleting posts). + * + * See http://symfony.com/doc/current/security/voters.html + * + * @author Yonel Ceruto + */ +class PostVoter extends Voter +{ + // Defining these constants is overkill for this simple application, but for real + // applications, it's a recommended practice to avoid relying on "magic strings" + const SHOW = 'show'; + const EDIT = 'edit'; + const DELETE = 'delete'; + + /** + * {@inheritdoc} + */ + protected function supports($attribute, $subject) + { + // this voter is only executed for three specific permissions on Post objects + return $subject instanceof Post && in_array($attribute, [self::SHOW, self::EDIT, self::DELETE]); + } + + /** + * {@inheritdoc} + */ + protected function voteOnAttribute($attribute, $post, TokenInterface $token) + { + $user = $token->getUser(); + + // the user must be logged in; if not, deny permission + if (!$user instanceof User) { + return false; + } + + // the logic of this voter is pretty simple: if the logged user is the + // author of the given blog post, grant permission; otherwise, deny it. + // (the supports() method guarantees that $post is a Post object) + return $user === $post->getAuthor(); + } +}