Permalink
Browse files

feature #442 Add a security Post Voter (yceruto, javiereguiluz)

This PR was merged into the master branch.

Discussion
----------

Add a security Post Voter

Implement #440 feature.

- [x] Add more code comments and doc references

Following some references and recommendations in:
- https://symfony.com/doc/current/components/security/authorization.html
- http://symfony.com/doc/current/security/voters.html
- https://stovepipe.systems/post/symfony-security-roles-vs-voters (@iltar blog)

Any suggestion is welcome!

Commits
-------

a592eea Minor reword in the help note
8468423 Simplified the voter code a bit
c14e9db Add security post voter
  • Loading branch information...
2 parents 12ff0ab + a592eea commit ebca80b199bedc73c8fb0a4476f4b271ac375124 @javiereguiluz javiereguiluz committed Jan 21, 2017
@@ -55,7 +55,7 @@
{% endblock %}
{% block sidebar %}
- {% if post.isAuthor(app.user) %}
+ {% if is_granted('edit', post) %}
<div class="section">
<a class="btn btn-lg btn-block btn-success" href="{{ path('admin_post_edit', { id: post.id }) }}">
<i class="fa fa-edit" aria-hidden="true"></i> {{ 'action.edit_post'|trans }}
@@ -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:
@@ -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)
{
@@ -0,0 +1,61 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * 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 <yonelceruto@gmail.com>
+ */
+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();
+ }
+}

0 comments on commit ebca80b

Please sign in to comment.