maximilienGilet / notification-bundle

A simple Symfony bundle to notify user
https://packagist.org/packages/mgilet/notification-bundle
MIT License
107 stars 58 forks source link

Current user control to access routes #44

Closed KCahuete closed 6 years ago

KCahuete commented 6 years ago

Hi Maximilien, I saw that everyone can access the routes, and see the notifications, mark them as seen, etc. Shouldn't there be some control to be sure that the current user is matching the notifiable entity?

KCahuete commented 6 years ago

If someone is looking for a way to do this, I manage to make this security check by adding a listener on kernel.controller.

services.yml

mybundle.listener.before_controller:
    class: MybundleBundle\Listener\BeforeControllerListener
    arguments: ["@security.token_storage", "@mgilet.notification"]
    tags:
        - { name: kernel.event_listener, event: kernel.controller, method: onKernelController }

BeforeControllerListener

<?php

namespace MybundleBundle\Listener;

use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Mgilet\NotificationBundle\Controller\NotificationController;
use Mgilet\NotificationBundle\Manager\NotificationManager;

class BeforeControllerListener
{
    protected $tokenStorage;
    protected $notificationManager;

    public function __construct(TokenStorage $tokenStorage, NotificationManager $notificationManager)
    {
        $this->tokenStorage = $tokenStorage;
        $this->notificationManager = $notificationManager;
    }

    public function onKernelController(FilterControllerEvent $event)
    {
        $controller = $event->getController();
        $request = $event->getRequest();

        if (!is_array($controller)) {
            // not an object but a different kind of callable. Do nothing
            return;
        }

        $controllerObject = $controller[0];

        // skip initializing for exceptions
        if ($controllerObject instanceof ExceptionController) {
            return;
        }

        /**
         * Security check of MgiletNotificationBundle
         * Not allowing access of controller if notifiable entity is not matching current user
         */
        if($controllerObject instanceof NotificationController && $notifiable = $request->attributes->get('notifiable')) {
            if($this->notificationManager->getNotifiableInterface($this->notificationManager->getNotifiableEntityById($notifiable)) != $this->getUser())
                throw new AccessDeniedException();
        }
    }

    /**
     * Get a user from the Security Token Storage.
     * Equivalent of controller getUser() method
     *
     * @return mixed
     */
    protected function getUser()
    {
        if (!$this->tokenStorage) {
            return;
        }

        if (null === $token = $this->tokenStorage->getToken()) {
            return;
        }

        if (!\is_object($user = $token->getUser())) {
            // e.g. anonymous authentication
            return;
        }

        return $user;
    }
}
maximilienGilet commented 6 years ago

This is a great example, thanks !

The bundle is intended to be used with any entity (not only users) so this could be a nice addition in the docs for common use cases 👍