Как безопасно вызвать действие удаления в контроллере?

Я создал блог с системой комментариев и хочу, чтобы автор или администратор удалил свой комментарий.

Итак, я поискал в Интернете, но нашел только посты со ссылкой на Symfony 2/3, и мне было трудно понять.

Поэтому я создал свою собственную функцию

/**
 * @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));
}

На ветке у меня есть эта ссылка:

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

Мне нужен идентификатор комментария для действия, а также идентификатор статьи и ярлык статьи, чтобы перенаправить пользователя после удаления комментария.

Я проверяю, что человек, который удаляет комментарий, является автором или модератором.

Однако я слышал, что это абсолютно небезопасно, потому что я должен использовать форму, но я действительно не знаю, как использовать форму в этом случае... Или, может быть, с помощью JS, чтобы скрыть ссылку на конечного пользователя?

Поэтому я хотел бы знать, достаточно ли безопасна моя функция или существует лучшее решение и как его реализовать?


person Community    schedule 10.03.2019    source источник
comment
Почему бы вам не реализовать js confirm для кнопки удаления? (я имею в виду, сделать его кнопкой и поместить обработчик событий в событие click, чтобы запустить функцию confirm и удалить, только если он возвращает true). Вот сайт, который объясняет немного лучше   -  person Nenri    schedule 10.03.2019
comment
Использование формы (или HTTP POST) вместо ссылки не является более безопасным. Скрытие ссылки ничего не защитит. Это называется безопасность через неизвестность и не должна использоваться.   -  person ferdynator    schedule 10.03.2019
comment
Я не хочу реализовывать подтверждение, я хочу, чтобы мой комментарий был удален напрямую. Ferdynator, значит, моя функция достаточно хороша и безопасна?   -  person    schedule 10.03.2019
comment
Например, может ли бот Google удалить комментарий с помощью этого метода?   -  person    schedule 10.03.2019
comment
Вы можете использовать форму с методом post и добавить защиту csrf. Чтобы проверить, что человек, удаляющий комментарий, является его автором или модератором, другим способом является использование symfony voicer, чтобы вы могли использовать его в своем методе контроллера для защиты действия и в шаблоне ветки, чтобы скрыть форму для пользователей, которые не имеют доступа.   -  person William Bridge    schedule 10.03.2019


Ответы (3)


Способ защитить ваше действие удаления, это сделать что-то вроде:


    <?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;
        }

    }

В вашей пользовательской сущности метод hasRole может выглядеть примерно так:

   /**
     * @param string $role
     */
    public function hasRole(string $role)
    {
        return in_array(strtoupper($role), $this->getRoles(), true);
    }
  • В вашем шаблоне вы можете сделать что-то вроде:
{% 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 %}

  • Наконец, в вашем контроллере вы можете сделать что-то вроде:

    /**
     * @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));
    }

Здесь ваш метод удаления защищен токеном csrf и избирателем. Я думаю, что это попытка решения.

person William Bridge    schedule 10.03.2019

Для решения подобных проблем я бы рекомендовал использовать Symfony Voters https://symfony.com/doc/current/security/voters.html

person Victor Kochkarev    schedule 10.03.2019
comment
StackOverflow настоятельно не рекомендует отвечать только ссылками по разным причинам. Если у вас нет времени опубликовать полный ответ (как это сделал Уильям Бридж), вы можете оставить его в виде комментария (когда вы наберете необходимые 50 очков репутации). Взгляните на весь раздел справки, чтобы узнать, как работает SO :-) - person gp_sflover; 11.03.2019

Вы движетесь в правильном направлении. Всегда выполняйте проверку и проверку разрешений в бэкэнде.

Скрытие ссылки или использование формы и отключение ее не помешает людям, использующим инструменты разработчика, отправить запрос на ваш контроллер. Я бы предпочел, чтобы внешние проверки служили удобству для пользователей, прямо показывая им, что некоторые данные недействительны / им не разрешено что-то делать, прежде чем делать запрос.

Я использую SensioFrameworkExtraBundle для проверки ROLE (до сих пор мне не нравятся аннотации для таких проверок.. хм) — выдает разрешениеDeniedException, если у пользователя нет подходящей роли для действия контроллера. После этого может потребоваться выполнить дальнейшие проверки, как вы сделали с $user->getId() != $comment->getAuthor()->getId()

person dahe    schedule 10.03.2019