0

I created a blog with a comment system, and I would like the author or administrator to delete his comment.

So I searched the internet, but I found only posts in reference to Symfony 2/3 and I had a hard time understanding.

So I created my own function

/**
 * @Route("/blog/commentDelete/{id}-{articleId}-{articleSlug}", name="comment_delete")
 */
public function commentDelete($id, $articleId, $articleSlug, CommentRepository $commentRepository, AuthorizationCheckerInterface $authChecker){

   $em = $this->getDoctrine()->getManager();
   $comment = $commentRepository->find($id);

    $user = $this->getUser();
    if ($user->getId() != $comment->getAuthor()->getId() && $authChecker->isGranted('ROLE_MODERATOR') == false ){
        throw exception_for("Cette page n'existe pas");
    }

   $em->remove($comment);
   $em->flush();
   $this->addFlash('comment_success', 'Commentaire supprimé avec succès');
   return $this->redirectToRoute('blog_show', array('id' => $articleId, 'slug' => $articleSlug));
}

On twig, I've this link:

<a href="{{ path('comment_delete', {'id': comment.id, 'articleId': article.id, 'articleSlug': article.slug}) }}">Supprimer</a>

I need the comment id for the action, and article id et article slug to redirect the user once the comment has been deleted.

I check that the person who delete the comment is the author or a moderator.

However, I heard that is absolutely not secure because I have to use a form, but I really don't know how to use a form in this case... Or maybe with JS to hide the link to the final user?

So I would like to know if my function is secure enough or if exists a better solution and how to implement it?

gp_sflover
  • 3,460
  • 5
  • 38
  • 48
  • Why wouldn't you implement a js `confirm` to your delete button ? (i mean, making it a button, and putting an eventhandler on the click event to run the `confirm` function and only delete if it returns `true`). [Here](https://www.w3schools.com/jsref/met_win_confirm.asp)'s a site that explains a bit better – Nenri Mar 10 '19 at 10:40
  • Using a form (or HTTP `POST`) instead of a link is not more secure. Hiding a link will not secure anything. It's called [security through obscurity](https://en.wikipedia.org/wiki/Security_through_obscurity) and should not be used. – ferdynator Mar 10 '19 at 10:41
  • I don't want to implement a confirmation, I want my comment to be deleted directly. Ferdynator so my function is good and secure enought ? –  Mar 10 '19 at 11:09
  • Can google bot delete a comment with this method for example ? –  Mar 10 '19 at 11:28
  • You can use form with post method and add csrf protection. To check that the person who delete the comment is the author or a moderator, another way is to use the symfony voter, so that you can use it in your controller method to protect the action and in twig template to hide the form for users who do not have access. – William Bridge Mar 10 '19 at 13:52

3 Answers3

1

A way to protect your delete action, it is to do something like :


    <?php

    namespace App\Security\Voter;

    use App\Entity\User;
    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    use Symfony\Component\Security\Core\Authorization\Voter\Voter;
    use App\Entity\Comment;

    class CommentVoter extends Voter
    {
        const CAN_DELETE = 'CAN_DELETE';

        protected function supports($attribute, $subject)
        {

            return in_array($attribute, [self::CAN_DELETE]) && $subject instanceof Comment;
        }

        protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
        {
            $user = $token->getUser();
            // if the user is anonymous, do not grant access
            if (!$user instanceof User) {
                return false;
            }

            /** @var Comment $comment */
            $comment = $subject;

            switch ($attribute) {
                case self::CAN_DELETE:
                    return $this->canDelete($comment, $user);
            }

            throw new \LogicException('This code should not be reached!');
        }

        private function canDelete(Comment $comment, User $user)
        {
            if($user->getId() !== $comment->getAuthor()->getId() && $user->hasRole('ROLE_MODERATOR') === false) {
                return false;  
            }

            return true;
        }

    }

In your user entity, the hasRole method can be something like :

   /**
     * @param string $role
     */
    public function hasRole(string $role)
    {
        return in_array(strtoupper($role), $this->getRoles(), true);
    }
  • In your template, you can do something like :
{% if is_granted('CAN_DELETE', comment) %}
    <form action="{{ path('comment_delete', {'id': comment.id, 'articleId': article.id, 'articleSlug': article.slug}) }}" method="post">
       <input type="hidden" name="_csrf_token" value="{{csrf_token('delete_comment')}}" />
       <button>supprimer</button>
    </form>
{% endif %}

  • Finally in your controller, you can do something like :

    /**
     * @Route("/blog/commentDelete/{id}-{articleId}-{articleSlug}", methods={"POST"}, name="comment_delete")
     */
    public function commentDelete($id, $articleId, $articleSlug, CommentRepository $commentRepository, EntityManagerInterface $em){

       $comment = $commentRepository->find($id);
       $csrfToken = $request->request->get('_csrf_token');

       if(!$this->isCsrfTokenValid('delete_comment', $csrfToken) || !$this->isGranted('CAN_DELETE', $comment){
           throw exception_for("Cette page n'existe pas");
       }

       $em->remove($comment);
       $em->flush();
       $this->addFlash('comment_success', 'Commentaire supprimé avec succès');
       return $this->redirectToRoute('blog_show', array('id' => $articleId, 'slug' => $articleSlug));
    }

Here your delete method is protected by the csrf token and the voter. I think this an attempt of solution.

William Bridge
  • 571
  • 3
  • 12
0

In order to solve this kind of problems I would recommend to use Symfony Voters https://symfony.com/doc/current/security/voters.html

  • 1
    Only link answers are strongly discouraged by StackOverflow for various reasons. If you don't have the time to post a complete answer (like wiliam bridge did) you can leave it as a comment (when you'll reached the needed 50 points of reputation). Take a look at the entire help section to learn how SO works :-) – gp_sflover Mar 11 '19 at 10:27
0

You are heading in the right direction. Always do validation and permission checks in the backend.

Hiding a link or using a form and setting it to disabled won't prevent people using dev-tools from sending the request to your controller. I'd rather see frontend checks as a convinience for users - directly showing them that some data is invalid / they are not allowed to do something, before making a request.

I am using the SensioFrameworkExtraBundle for ROLE checks (still i don't like annotations for such checks.. hmm) - throwing a permissionDeniedException if an user hasn't got the fitting role for the controllers action. Following it can be needed to do further checks like you did with $user->getId() != $comment->getAuthor()->getId()

dahe
  • 806
  • 8
  • 13