From c14e9dbc71e32634e5b2002633afabe5f2c320d8 Mon Sep 17 00:00:00 2001 From: Yonel Ceruto Date: Tue, 17 Jan 2017 12:10:05 -0500 Subject: [PATCH 1/3] Add security post voter --- app/Resources/views/blog/post_show.html.twig | 2 +- app/config/services.yml | 8 +++ .../Controller/Admin/BlogController.php | 16 ++--- src/AppBundle/Security/PostVoter.php | 65 +++++++++++++++++++ 4 files changed, 79 insertions(+), 12 deletions(-) create mode 100644 src/AppBundle/Security/PostVoter.php 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..dbfa20fa7 --- /dev/null +++ b/src/AppBundle/Security/PostVoter.php @@ -0,0 +1,65 @@ + + * + * 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; + +/** + * Decide whether the current user can show, edit or delete a Post object. + * + * See http://symfony.com/doc/current/security/voters.html + * + * @author Yonel Ceruto + */ +class PostVoter extends Voter +{ + const SHOW = 'show'; + const EDIT = 'edit'; + const DELETE = 'delete'; + + /** + * {@inheritdoc} + */ + protected function supports($attribute, $subject) + { + // if the attribute isn't one we support, return false + if (!in_array($attribute, [self::SHOW, self::EDIT, self::DELETE])) { + return false; + } + + // only vote on Post objects inside this voter + if (!$subject instanceof Post) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + protected function voteOnAttribute($attribute, $post, TokenInterface $token) + { + $user = $token->getUser(); + + if (!$user instanceof User) { + // the user must be logged in; if not, deny access + return false; + } + + // you know $post is a Post object, thanks to supports + return $user === $post->getAuthor(); + } +} From 8468423e20c4d4cf5830dac8abd57b0cb458e41f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 20 Jan 2017 17:23:31 +0100 Subject: [PATCH 2/3] Simplified the voter code a bit --- src/AppBundle/Security/PostVoter.php | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/AppBundle/Security/PostVoter.php b/src/AppBundle/Security/PostVoter.php index dbfa20fa7..49df8397f 100644 --- a/src/AppBundle/Security/PostVoter.php +++ b/src/AppBundle/Security/PostVoter.php @@ -17,7 +17,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; /** - * Decide whether the current user can show, edit or delete a Post object. + * 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 * @@ -25,6 +26,8 @@ */ 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'; @@ -34,17 +37,8 @@ class PostVoter extends Voter */ protected function supports($attribute, $subject) { - // if the attribute isn't one we support, return false - if (!in_array($attribute, [self::SHOW, self::EDIT, self::DELETE])) { - return false; - } - - // only vote on Post objects inside this voter - if (!$subject instanceof Post) { - return false; - } - - return true; + // 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]); } /** @@ -54,12 +48,14 @@ protected function voteOnAttribute($attribute, $post, TokenInterface $token) { $user = $token->getUser(); + // the user must be logged in; if not, deny permission if (!$user instanceof User) { - // the user must be logged in; if not, deny access return false; } - // you know $post is a Post object, thanks to supports + // the logic of this voter is pretty simple: if the given user is the + // author of the blog post, grant permission; otherwise, deny it. + // (the supports() method guarantees that $post is a Post object) return $user === $post->getAuthor(); } } From a592eeacab5aa4771282f1f8316625000980725f Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Sat, 21 Jan 2017 15:31:21 +0100 Subject: [PATCH 3/3] Minor reword in the help note --- src/AppBundle/Security/PostVoter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AppBundle/Security/PostVoter.php b/src/AppBundle/Security/PostVoter.php index 49df8397f..cd2269fe7 100644 --- a/src/AppBundle/Security/PostVoter.php +++ b/src/AppBundle/Security/PostVoter.php @@ -53,8 +53,8 @@ protected function voteOnAttribute($attribute, $post, TokenInterface $token) return false; } - // the logic of this voter is pretty simple: if the given user is the - // author of the blog post, grant permission; otherwise, deny it. + // 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(); }